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

Apple Ads (AA) attribution, Adapty.activate() çağrısından sonra asenkron olarak ulaşır. getFlow fonksiyonunu erken çağırırsanız, attribution henüz gelmemiş olabilir ve Adapty, placement’ı varsayılan kitle üzerinden çözümleyerek AA segmentine göre yapılandırılmış paywalllarınızı atlayabilir. AdaptyProfile.appliedExternalAttributionProviders, uygulamanın AA attribution’ının profile ne zaman uygulandığını tespit etmesini sağlar; böylece paywall isteği, AA segmentasyonunun doğru şekilde çözümleneceği ana kadar bekleyebilir.

Important

appliedExternalAttributionProviders yalnızca Apple Ads’i raporlar. Diğer sağlayıcılardan gelen attribution, kullanıcı profillerinde görülebilir ve segment filtrelerinde kullanılabilir; ancak henüz burada görünmez.

Başlamadan önce

İhtiyacınız olanlar:

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

Örnekler, SDK 4.1’deki API adlarını kullanmaktadır. SDK 4.0’da profil özelliğinin adı appliedAttributionSources’tır. SDK 3.x’te bu özellik aynı eski ada sahiptir ve paywall’lar getPaywall/getPaywallForDefaultAudience ile getirilir; bunlar bir AdaptyPaywall döndürür. Bkz. Adapty iOS SDK’yı v4.1’e taşıma.

Nasıl çalışır

Adapty.activate() çağrısının ardından SDK, arka planda Apple’dan Apple Ads attribution verisini ister ve sonucu Adapty’nin backend’ine iletir. AA, profil için etkin 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 gelmemiştir.
  • Attribution başka bir sağlayıcıdan gelmiştir; bu dizi onu raporlamaz.

Boş bir dizi ile bile getFlow çağırmak güvenlidir — Adapty, isteği mevcut profil durumuyla eşleşen kitleye göre çözümler; genellikle bu varsayılan kitledir.

Important

Bu 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, önbellekteki profil zaten appliedExternalAttributionProviders içinde .appleAds değerini taşır; didLoadLatestProfile bu değerle hemen tetiklenir ve getFlow, Apple Ads segmentine uygun paywall’ı hiç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 the paywall via Adapty.getFlow(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.

Tam örnek

Aşağıdaki implementasyon, attribution’ı bir zaman aşımıyla yarıştırır, varsayılan kitleye ait paywall’ı paralel olarak önceden çeker ve uygun olan paywall’ı döndürür. Çağıran taraf tek bir async fonksiyonu bekler; çağrı noktasında yönetilecek delegate veya durum bayrağı yoktur.

ProfileObserver, AdaptyDelegate’ten gelen profil güncellemelerini yayımlayan yeniden kullanılabilir bir singleton’dır. FlowLoader.getFlowOrDefault, yapılandırılmış eşzamanlılık TaskGroup kullanarak bu yarışı çalıştırır:

  • Attribution timeout süresi içinde gelirse, segmentlenmiş paywall’ı getFlow(placementId:) aracılığıyla döndürür.
  • timeout süresi önce dolarsa, önceden alınan varsayılan kitle paywall’ını getFlowForDefaultAudience(placementId:) aracılığıyla döndürü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 FlowLoader {
    static func getFlowOrDefault(
        placementId: String,
        timeout: TimeInterval
    ) async throws -> AdaptyFlow {
        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 defaultFlowTask = Task {
            try await Adapty.getFlowForDefaultAudience(placementId: placementId)
        }

        do {
            // Race two child tasks: whichever finishes first wins.
            let result = try await withThrowingTaskGroup(of: AdaptyFlow.self) { group in
                // 1. Wait for attribution, then ask Adapty for the segmented paywall.
                group.addTask {
                    await waitForAttribution()
                    return try await Adapty.getFlow(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.
            defaultFlowTask.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 defaultFlowTask.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 flow = try await FlowLoader.getFlowOrDefault(
        placementId: "YOUR_PLACEMENT_ID",
        timeout: 5
    )
    // present the paywall
} catch {
    // handle the error or show a fallback paywall
}

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.