Skip to content

Commit

Permalink
Keeps a strong reference to the RCPurchasesDelegateProtocol, to avoid…
Browse files Browse the repository at this point in the history
… it being deallocated unintentionally.
  • Loading branch information
JayShortway committed Sep 19, 2024
1 parent aa4378b commit 54ce099
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ public expect class Purchases {
/**
* The delegate is responsible for handling promotional product purchases (App Store only) and
* changes to customer information.
*
* **Note:** If your delegate is not a singleton, make sure you set this back to null when
* you're done to avoid memory leaks. For instance, if your delegate is tied to a screen, set
* this to null when the user navigates away from the screen.
*/
public var delegate: PurchasesDelegate?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public actual class Purchases private constructor(private val iosPurchases: IosP

public actual var delegate: PurchasesDelegate?
get() = iosPurchases.delegate()?.toPurchasesDelegate()
set(value) = iosPurchases.setDelegate(value?.toRcPurchasesDelegate())
set(value) = iosPurchases.setDelegate(value.toRcPurchasesDelegate())

public actual val isAnonymous: Boolean
get() = iosPurchases.isAnonymous()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import platform.darwin.NSObject
internal fun RCPurchasesDelegateProtocol.toPurchasesDelegate(): PurchasesDelegate =
(this as PurchasesDelegateWrapper).wrapped

internal fun PurchasesDelegate.toRcPurchasesDelegate(): RCPurchasesDelegateProtocol =
PurchasesDelegateWrapper(this)
internal fun PurchasesDelegate?.toRcPurchasesDelegate(): RCPurchasesDelegateProtocol? =
this?.let { PurchasesDelegateWrapper(it) }
.also { PurchasesDelegateStrongReference.delegate = it }

private class PurchasesDelegateWrapper(val wrapped: PurchasesDelegate) :
RCPurchasesDelegateProtocol,
Expand Down Expand Up @@ -45,3 +46,20 @@ private class PurchasesDelegateWrapper(val wrapped: PurchasesDelegate) :
}

}

/**
* On the iOS platform side, the backing field of `Purchases.delegate`, `Purchases.privateDelegate`,
* is a `weak var`. On the multiplatform side, we wrap the actual delegate in
* `PurchasesDelegateWrapper` to conform to `RCPurchasesDelegateProtocol`. Since that is a private
* implementation detail, the only reference held to our wrapper is the `weak var`. This means that
* it gets deallocated the first chance it gets. For this reason, we keep a strong reference to our
* `PurchasesDelegateWrapper` here. This matches the Android behavior, which keeps a strong
* reference on the platform side already.
*
* **Note**: we cannot make our `PurchasesDelegateWrapper` an `object` directly, as objects cannot
* extend `NSObject`. See also
* [KT67930](https://youtrack.jetbrains.com/issue/KT-67930/Getting-Crash-while-building-KMM-project-with-XCode-15.3#focus=Comments-27-9796215.0-0)
*/
private object PurchasesDelegateStrongReference {
var delegate: RCPurchasesDelegateProtocol? = null
}

0 comments on commit 54ce099

Please sign in to comment.