Mostrar un paywall segmentado por Apple Ads en el primer lanzamiento
Apple Ads (AA) la atribución llega de forma asíncrona después de Adapty.activate(). Si llamas a getPaywall demasiado pronto, la atribución aún no ha llegado y Adapty resuelve el placement contra la audiencia predeterminada, saltándose tus paywalls segmentados por AA. AdaptyProfile.appliedAttributionSources permite a la app detectar cuándo se ha aplicado la atribución de AA al perfil, de modo que la solicitud del paywall pueda esperar hasta que la segmentación de AA se resuelva correctamente.
Antes de empezar
Necesitas:
- Adapty iOS SDK 3.17.1 o posterior.
- Apple Ads configurado para la app en Adapty. Consulta Apple Ads.
Cómo funciona
Tras Adapty.activate(), el SDK solicita en segundo plano la atribución de Apple Ads a Apple y reenvía el resultado al backend de Adapty. Cuando AA se convierte en la fuente de atribución activa del perfil, el SDK entrega un AdaptyProfile actualizado cuyo array appliedAttributionSources contiene .appleAds.
Un array vacío puede significar cualquiera de estas situaciones:
- La atribución de Apple Ads aún no se ha procesado para este perfil.
- No ha llegado ninguna atribución.
Incluso con un array vacío,
getPaywallsigue siendo seguro de llamar — Adapty resuelve la solicitud con la audiencia que coincida con el estado actual del perfil, normalmente la audiencia por defecto.
La espera solo aplica en el primer lanzamiento. Una vez que la atribución de Apple Ads ha sido registrada, queda almacenada permanentemente en el perfil. En cada lanzamiento posterior, el perfil en caché ya incluye .appleAds en appliedAttributionSources, didLoadLatestProfile se dispara con ese valor de inmediato, y getPaywall devuelve el paywall segmentado por Apple Ads sin ningún retraso.
Implementación
En el primer lanzamiento, controla la aparición de .appleAds en el perfil y aplica un timeout estricto: si la atribución de Apple Ads nunca llega, esos usuarios igualmente deben ver un paywall.
- Activa el SDK. Consulta Instalar y configurar el SDK de iOS.
- Suscríbete a las actualizaciones del perfil implementando
AdaptyDelegatey el métododidLoadLatestProfile. Si todavía no has configurado el delegate, consulta Escuchar actualizaciones de suscripción. - Observa
.appleAdsenappliedAttributionSources. Cuando aparezca, solicita el paywall — Adapty devolverá la variante segmentada por AA:
extension <YourAdaptyDelegateImpl>: AdaptyDelegate {
nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) {
if profile.appliedAttributionSources.contains(where: { $0 == .appleAds }) {
// load paywall via Adapty.getPaywall(placementId:)
}
}
}
- Inicia un temporizador de 3 a 5 segundos en paralelo con la suscripción. Si el temporizador se dispara antes de que aparezca
.appleAds, solicita el paywall de todas formas: Cualquiera de los dos caminos que se active primero debe cargar el paywall; el otro debe ignorarse. Usa un único indicador de estado (por ejemplo,hasLoadedPaywall) para deduplicar y evitar que el paywall se solicite dos veces. Configura un paywall de respaldo para el placement para que el usuario nunca se quede bloqueado si la solicitud de red falla.
Ejemplo completo
La implementación que sigue ejecuta en paralelo la espera de la atribución con un timeout y la precarga del paywall de la audiencia por defecto, devolviendo el paywall apropiado según el resultado. El código que llama solo necesita hacer await a una única función asíncrona: sin delegados ni flags de estado en el punto de llamada.
ProfileObserver es un singleton reutilizable que publica actualizaciones del perfil desde AdaptyDelegate. PaywallLoader.getPaywallOrDefault ejecuta la carrera mediante un TaskGroup de concurrencia estructurada:
- Si la atribución llega dentro del
timeout, devuelve el paywall segmentado mediantegetPaywall(placementId:). - Si el
timeoutexpira primero, devuelve el paywall de la audiencia predeterminada prefetchado mediantegetPaywallForDefaultAudience(placementId:).
/// Demuestra cómo obtener un paywall que depende de que se aplique la atribución,
/// usando como respaldo el paywall de la audiencia predeterminada si la atribución
/// no llega a tiempo.
///
/// Sin estado y autocontenido: cada llamada inicia su propia solicitud anticipada
/// para la audiencia predeterminada y la compite contra la obtención segmentada
/// con atribución.
enum PaywallLoader {
static func getPaywallOrDefault(
placementId: String,
timeout: TimeInterval
) async throws -> AdaptyPaywall {
struct TimedOut: Error {}
// Iniciamos la solicitud de audiencia predeterminada de inmediato para que
// tenga toda la ventana de `timeout` para cargarse. La cancelaremos si
// hay éxito, o esperaremos su resultado si se agota el tiempo; nunca
// habrá una llamada de red duplicada.
let defaultPaywallTask = Task {
try await Adapty.getPaywallForDefaultAudience(placementId: placementId)
}
do {
// Competimos dos tareas secundarias: gana la que termine primero.
let result = try await withThrowingTaskGroup(of: AdaptyPaywall.self) { group in
// 1. Esperamos la atribución y luego pedimos a Adapty el paywall segmentado.
group.addTask {
await waitForAttribution()
return try await Adapty.getPaywall(placementId: placementId)
}
// 2. Temporizador: lanza `TimedOut` tras `timeout` segundos.
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() // detiene la tarea perdedora (el temporizador o la espera de atribución).
return value
}
// Ganó el paywall segmentado: ya no necesitamos la solicitud anticipada de la audiencia predeterminada.
defaultPaywallTask.cancel()
return result
} catch is TimedOut {
// La atribución no se aplicó a tiempo: devolvemos el resultado anticipado
// de la audiencia predeterminada (instantáneo si ya terminó; si no,
// esperamos la solicitud en curso).
return try await defaultPaywallTask.value
}
}
/// Suspende hasta que se observa un perfil con la fuente de atribución deseada.
/// `@Published.values` emite el perfil actual de inmediato al suscribirse,
/// por lo que retorna en la primera iteración si la atribución ya está aplicada.
@MainActor
private static func waitForAttribution() async {
for await profile in ProfileObserver.shared.$profile.values {
if profile?.appliedAttributionSources.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
}
}
}
Conecta ProfileObserver a AdaptyDelegate una vez, después de que Adapty.activate() termine:
Adapty.delegate = ProfileObserver.shared
Llámalo desde la pantalla de inicio:
do {
let paywall = try await PaywallLoader.getPaywallOrDefault(
placementId: "YOUR_PLACEMENT_ID",
timeout: 5
)
// present the paywall
} catch {
// handle the error or show a fallback paywall
}
Si tu app ya usa un AdaptyDelegate para otros fines (por ejemplo, escuchar actualizaciones de suscripción), reenvía didLoadLatestProfile a ProfileObserver.shared desde tu delegado existente en lugar de establecer Adapty.delegate = ProfileObserver.shared.