# Threading & Object Lifetime

Two rules prevent most integration bugs with OnsideKit: **call it from the main actor**, and **keep a strong reference to the objects you hand it**.

## Threading: everything UI-facing is `@MainActor`

OnsideKit's entry points and callbacks are main-actor isolated:

* The `Onside` facade is `@MainActor`.
* `OnsidePaymentQueue`, `OnsideProductsRequest`, and `OnsideSignedInAppsHistoryRequest` — and all of their methods — are `@MainActor`.
* All delegate and observer methods (`OnsideDelegate`, `OnsidePaymentQueueDelegate`, `OnsidePaymentTransactionObserver`, `OnsideProductsRequestDelegate`, `OnsideSignedInAppsHistoryRequestDelegate`) are `@MainActor`.

In practice: **call OnsideKit from the main actor, and expect every callback and completion handler on the main actor.** You can touch UIKit directly inside them without hopping queues.

The value types you receive are `Sendable` and not actor-isolated, so they are safe to pass to background work:

* Models — `OnsideProduct`, `OnsidePayment`, `OnsidePaymentTransaction`, `OnsideStorefront`, `OnsidePrice`, `OnsidePeriod`, `OnsideProductsResponse`, `OnsideAttributionMetadata`, `OnsideSignedInAppsHistory`.
* Error enums — all `Onside*Error` types are `Sendable` (and `Codable`).

## Object lifetime: what you must retain

OnsideKit holds your callback objects **weakly**. If you don't keep a strong reference, they are deallocated and you stop receiving callbacks.

### Request objects — retain until they finish

`Onside.makeProductsRequest(...)` and `Onside.makeSignedInAppsHistoryRequest()` return request objects that **you** own. The SDK does not keep them alive.

```swift
final class ProductLoader: OnsideProductsRequestDelegate {
    private var request: OnsideProductsRequest?   // strong reference

    func load() {
        let request = Onside.makeProductsRequest(productIdentifiers: ["my.product"])
        request.delegate = self
        self.request = request   // retain for the whole request
        request.start()
    }
}
```

Use `stop()` to cancel an in-flight request; a cancelled request reports `.cancelled` to its failure delegate.

### Observers — retain and remove

The payment queue holds observers weakly. Keep a strong reference to your observer, and balance `add(observer:)` with `remove(observer:)` when the observer goes away.

```swift
final class StoreCoordinator: OnsidePaymentTransactionObserver {
    init()  { Onside.defaultPaymentQueue().add(observer: self) }
    deinit  { Onside.defaultPaymentQueue().remove(observer: self) }

    func onsidePaymentQueue(
        _ queue: OnsidePaymentQueue,
        updatedTransactions: [OnsidePaymentTransaction]
    ) { /* ... */ }
}
```

{% hint style="info" %}
A long-lived owner (an app-level coordinator or your `AppDelegate`) is the natural place to hold the queue observer, so it stays alive for the whole app session and never misses a transaction.
{% endhint %}

### Delegates — retain them too

`Onside.delegate` ([`OnsideDelegate`](/sdk/customization/delegate.md)) and `Onside.defaultPaymentQueue().delegate` ([`OnsidePaymentQueueDelegate`](/sdk/purchasing/storefront-price-changes.md)) are weak references. Assign an object you keep alive — typically your `AppDelegate` or an app-level object.

## Switching over OnsideKit enums

OnsideKit ships as a binary framework built for library evolution, so its public enums are **resilient** (non-frozen). When you `switch` over one — such as `OnsidePaymentTransactionState` or `OnsidePeriod` — include an `@unknown default` to stay forward-compatible with future cases:

```swift
switch transaction.transactionState {
case .purchasing: break
case .purchased, .restored: handlePurchase(transaction)
case .failed: handleFailure(transaction)
@unknown default: break
}
```

## Checklist

* ✅ Call OnsideKit from the main actor; handle callbacks on the main actor.
* ✅ Retain request objects until they finish; `stop()` to cancel.
* ✅ Retain observers and delegates; `remove(observer:)` when done.
* ✅ Add an `@unknown default` to every `switch` over an OnsideKit enum.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.onside.io/sdk/core-concepts/threading-and-retention.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
