iOS SDK'da ilk başlatmada AA hedefli paywall gösterme

Apple Ads (AA) attribution, Adapty.activate() çağrısından sonra asenkron olarak gelir. getPaywall’ı erken çağırırsanız, attribution henüz gelmemiş olabilir ve Adapty, placement’ı varsayılan kitleye göre çözümler — AA segmentli paywalllarınızı atlayarak. AdaptyProfile.appliedExternalAttributionProviders, uygulamanın AA attribution’ının profile ne zaman uygulandığını tespit etmesine olanak tanır; böylece paywall isteği, AA segmentasyonunun doğru şekilde çözümleneceği ana kadar bekleyebilir.

Başlamadan önce

İhtiyacınız olanlar:

  • Adapty iOS SDK 3.17.1 veya üzeri.
  • Adapty’de uygulama için yapılandırılmış Apple Ads. Bkz. Apple Ads.
Note

Aşağıdaki örnekler, SDK 4.1+ özellik adı olan appliedExternalAttributionProviders kullanmaktadır. SDK 3.17.1–4.0 sürümlerinde bu özellik appliedAttributionSources olarak adlandırılır.

Nasıl çalışır

Adapty.activate() çağrısının ardından SDK, arka planda Apple’dan Apple Ads attribution verisini talep eder ve sonucu Adapty’nin backend’ine iletir. AA, profil için aktif attribution kaynağı haline geldiğinde SDK, appliedExternalAttributionProviders dizisinde .appleAds içeren güncellenmiş bir AdaptyProfile iletir.

Boş bir dizi şu anlamlara gelebilir:

  • Bu profil için Apple Ads attribution henüz işlenmemiştir.
  • Hiç attribution verisi ulaşmamıştır.

Boş bir dizi ile bile getPaywall çağrısı güvenlidir — Adapty, isteği mevcut profil durumuna uyan kitleye göre çözümler; genellikle varsayılan kitle kullanılır.

Important

Bekleme yalnızca ilk başlatma için geçerlidir. Apple Ads attribution bir kez kaydedildikten sonra, profilde kalıcı olarak saklanır. Sonraki her başlatmada, önbelleğe alınmış profil zaten appliedExternalAttributionProviders içinde .appleAds değerini taşır; didLoadLatestProfile bu değerle anında tetiklenir ve getPaywall, Apple Ads segmentine göre uyarlanmış paywall’ı herhangi bir gecikme olmadan döndürür.

Uygulama

İlk açılışta profilde .appleAds değerini izleyin ve sert bir zaman aşımı uygulayın — Apple Ads attribution hiç gelmese bile, o kullanıcıların yine de bir paywall görmesi gerekir.

  1. SDK’yı etkinleştirin. Bkz. iOS SDK’yı yükle ve yapılandır.
  2. Profil güncellemelerine abone olun: AdaptyDelegate’i uygulayarak didLoadLatestProfile’ı implemente edin. Delegate’i henüz ayarlamadıysanız bkz. Abonelik güncellemelerini dinle.
  3. appliedExternalAttributionProviders içinde .appleAds çıkıp çıkmadığını takip edin. Göründüğünde paywall’ı isteyin — Adapty, AA segmentine göre belirlenmiş varyantı döndürür:
extension <YourAdaptyDelegateImpl>: AdaptyDelegate {
    nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) {
        if profile.appliedExternalAttributionProviders.contains(where: { $0 == .appleAds }) {
            // load paywall via Adapty.getPaywall(placementId:)
        }
    }
}
  1. Abonelikle paralel olarak 3–5 saniyelik bir zamanlayıcı başlatın. Zamanlayıcı .appleAds görünmeden tetiklenirse, yine de paywallı isteyin: Hangi yol önce tetiklenirse o ywall’ı yüklemeli; diğer yol atlanmalıdır. Paywall’ın iki kez getirilmemesi için tek bir durum bayrağı (örneğin hasLoadedPaywall) kullanarak yinelenenleri ortadan kaldırın. Ağ isteği başarısız olduğunda kullanıcının askıda kalmaması için placement’a bir yedek paywall yapılandırın.

Eksiksiz örnek

Aşağıdaki implementasyon, attribution ile bir zaman aşımını yarıştırır, varsayılan kitle paywallını paralel olarak önceden çeker ve uygun olan paywallı döndürür. Çağıran taraf yalnızca tek bir async fonksiyonu bekler; çağrı noktasında yönetilmesi gereken delegate veya durum bayrağı yoktur.

ProfileObserver, AdaptyDelegate’ten gelen profil güncellemelerini yayınlayan yeniden kullanılabilir bir singleton’dır. PaywallLoader.getPaywallOrDefault ise yarışı yapılandırılmış eşzamanlılık TaskGroup kullanarak yürütür:

  • Attribution timeout süresi içinde gelirse, getPaywall(placementId:) aracılığıyla segmentlere ayrılmış paywall döndürülür.
  • timeout süresi önce dolarsa, getPaywallForDefaultAudience(placementId:) aracılığıyla önceden yüklenmiş varsayılan kitle paywall’u döndürülür.

/// Demonstrates how to fetch a paywall that depends on attribution being applied,
/// falling back to the default-audience paywall if attribution doesn't arrive in time.
///
/// Stateless and self-contained: every call kicks off its own default-audience
/// prefetch and races it against attribution + segmented fetch.
enum PaywallLoader {
    static func getPaywallOrDefault(
        placementId: String,
        timeout: TimeInterval
    ) async throws -> AdaptyPaywall {
        struct TimedOut: Error {}

        // Kick off the default-audience request immediately so it has the full
        // `timeout` window to load. We'll either cancel it on success or await
        // its result on timeout — never a duplicate network call.
        let defaultPaywallTask = Task {
            try await Adapty.getPaywallForDefaultAudience(placementId: placementId)
        }

        do {
            // Race two child tasks: whichever finishes first wins.
            let result = try await withThrowingTaskGroup(of: AdaptyPaywall.self) { group in
                // 1. Wait for attribution, then ask Adapty for the segmented paywall.
                group.addTask {
                    await waitForAttribution()
                    return try await Adapty.getPaywall(placementId: placementId)
                }
                // 2. Time-bomb: throws `TimedOut` after `timeout` seconds.
                group.addTask {
                    try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000))
                    throw TimedOut()
                }
                guard let value = try await group.next() else { throw CancellationError() }
                group.cancelAll() // stop the loser (sleeper or the attribution wait).
                return value
            }
            // Segmented paywall won — we no longer need the default-audience prefetch.
            defaultPaywallTask.cancel()
            return result
        } catch is TimedOut {
            // Attribution didn't apply in time — return the prefetched default
            // (instant if already done, otherwise we await the in-flight request).
            return try await defaultPaywallTask.value
        }
    }

    /// Suspends until a profile with the desired attribution source is observed.
    /// `@Published.values` emits the current profile immediately on subscription,
    /// so this returns on the first iteration if attribution is already applied.
    @MainActor
    private static func waitForAttribution() async {
        for await profile in ProfileObserver.shared.$profile.values {
            if profile?.appliedExternalAttributionProviders.contains(.appleAds) == true { return }
        }
    }
}

@MainActor
final class ProfileObserver: AdaptyDelegate {
    static let shared = ProfileObserver()

    @Published private(set) var profile: AdaptyProfile?

    nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) {
        Task { @MainActor [weak self] in
            self?.profile = profile
        }
    }
}

Adapty.activate() tamamlandıktan sonra ProfileObserver’ı AdaptyDelegate’e bir kez bağlayın:

Adapty.delegate = ProfileObserver.shared

Splash ekranından çağırın:

do {
    let paywall = try await PaywallLoader.getPaywallOrDefault(
        placementId: "YOUR_PLACEMENT_ID",
        timeout: 5
    )
    // paywallı göster
} catch {
    // hatayı yönet veya yedek paywall göster
}

Uygulamanız başka amaçlar için zaten bir AdaptyDelegate kullanıyorsa (örneğin, abonelik güncellemelerini dinlemek için), Adapty.delegate = ProfileObserver.shared yapmak yerine mevcut delegate’inizden didLoadLatestProfile’ı ProfileObserver.shared’a iletin.