Guia de implementação de SKAdNetwork 3 de servidor para servidor

Documento
Para implementar a versão mais recente da SKAdNetwork, consulte o Guia de implementação da SKAdNetwork 4.

Visão geral da SKAdNetwork

A SKAdNetwork é uma estrutura fornecida pela Apple para permitir a medição de campanhas publicitárias de instalação de aplicativos no iOS sem comprometer a privacidade(saiba mais). A estrutura ajuda a medir as taxas de conversão das campanhas de instalação de aplicações sem comprometer os identificadores dos utilizadores.

Como funciona a SKAdNetwork?

Na SKAdNetwork, o processo de atribuição é conduzido pela App Store através dos servidores da Apple. A informação de atribuição é depois desligada dos identificadores dos utilizadores e da informação temporal e enviada para a rede.

Screen_Shot_2020-09-16_at_18.57.56.png

Quando um anúncio é clicado e a loja é aberta, a aplicação de publicação e a rede fornecem algumas informações básicas, como a rede, o editor e o ID da campanha. Se a aplicação do anunciante tiver sido lançada e registada na SKAdNetwork, o dispositivo enviará uma notificação de conversão bem sucedida para a rede. O dispositivo comunicará os valores anexados juntamente com um valor de conversão que pode ser comunicado pela aplicação anunciada.

Essa notificação será enviada pelo menos 24 horas após o primeiro lançamento e não conterá quaisquer informações de identificação do dispositivo ou do utilizador.

Além disso, a App Store conduz o processo de modo a que a aplicação anunciada não tenha conhecimento do anúncio original e do editor. Desta forma, a rede é notificada de uma instalação sem saber nada sobre o utilizador que a instala.

O que esperar ao usar a SKAdNetwork?

A SKAdNetwork tem algumas vantagens importantes. Fornece-lhe todas as informações seguintes:

  • Atribuição do último clique que funciona sem consentimento
  • Detalhamento de fontes, campanhas e editores
  • Valores de conversão pós-instalação (até 64 valores discretos)
  • Assinaturas criptográficas para validar as instalações

No entanto, na sua forma atual, a SKAdNetwork é simples e requer uma implementação e coordenação cuidadosa entre várias entidades para garantir o seu funcionamento.

Aqui estão algumas das limitações actuais da SKAdNetwork:

  • Sem dados ao nível do utilizador
  • Sem atribuição de visualização
  • Gama limitada de valores de conversão:
    • Um único evento de conversão por instalação/reinstalação
    • Até 64 valores de conversão (6 bits)
  • Granularidade limitada:
    • Até 100 valores de campanha
    • Nenhuma representação para o grupo de anúncios e o nível criativo
  • Sem LTV / coortes longas
  • Exposição a fraudes:
    • O valor de conversão em si não é assinado (pode ser manipulado)
    • Os postbacks podem ser duplicados.

Para ultrapassar alguns destes problemas, a Singular lançou uma norma pública para a implementação da SKAdNetwork, bem como várias publicações no blogue que o podem ajudar a navegar na SKAdNEtwork:

Implementação da SKAdNetwork S2S

A solução Singular SKAdNetwork consiste nos seguintes componentes:

  • Código do lado do cliente para implementar a SKAdNetwork
  • Validação e agregação de postback de todas as redes
  • Proteção contra fraudes:
    • Validação de assinaturas e redução do ID da transação.
    • Configuração segura com as redes para verificar os parâmetros que não são assinados (valor de conversão e geodados).
  • Gestão do valor de conversão: permite configurar dinamicamente no dashboard da Singular qual a atividade pós-instalação que deve ser codificada no valor de conversão SKAdNetwork.
  • Relatórios: Traduzir o ID limitado da campanha SKAdNetwork e enriquecer os dados com mais parâmetros e granularidades de marketing
  • Postbacks de parceiros: Fornece o envio de postbacks SKAdNetwork com um valor de conversão descodificado em eventos e receitas, o que é fundamental para a sua otimização.

Para os clientes que utilizam uma integração Singular de servidor para servidor, a implementação de SKAdNetwork consiste em duas partes principais:

  1. Implementação do lado do cliente de SKAdNetwork: Esta parte é fundamental para registar as suas aplicações na SKAdNetwork e gerir o valor de conversão da SKAdNetwork de forma inteligente. Isto significa que, ao implementá-la, será capaz de otimizar as suas campanhas com base nas atribuições de SKAdNetwork e nas actividades pós-instalação associadas.
  2. Atualização da integração S2S (opcional): Esta parte é importante para a validação e resolução de problemas da implementação do lado do cliente. Ao enriquecer os eventos e sessões enviados para o SIngular hoje através dos endpoints de sessão e evento S2S do Singular com dados da SKAdNetwork (valores de conversão e carimbos de data/hora de atualização), o Singular pode validar se a implementação foi feita corretamente no lado do aplicativo.

Implementação do lado do cliente da SKAdNetwork

A Singular fornece exemplos de código que suportam o registo na SKAdNetwork e a gestão do valor de conversão. Esses exemplos de código são responsáveis pelas seguintes partes:

  1. Suporte e registo de SKAdnetwork
  2. Gestão do valor de conversão:
    • O código comunica de forma síncrona com um endpoint da Singular para receber o próximo valor de conversão com base no modelo de conversão configurado. Comunica eventos/sessões/receitas e, em resposta, obtém o próximo valor de conversão, que é um número codificado que representa a atividade pós-instalação que foi configurada para medição no painel de controlo do Singular.
    • O código também recolhe metadados SKAdnetwork que são utilizados para validação e cálculo do valor da próxima conversão:
      • O carimbo de data/hora da primeira chamada para a estrutura SKAdNetwork subjacente
      • O carimbo de data/hora da última chamada à estrutura SKAdNetwork subjacente
      • O último valor de conversão atualizado

Atualização da integração S2S

Obrigatório

Assim que tiver um modelo de conversão ativo, os nossos pontos finais S2S começarão a devolver um novo campo int chamado "conversion_value", que conterá o próximo valor a atualizar no código do lado do cliente.

Opcional

Para validar a integração e solucionar possíveis problemas de implementação, recomendamos o uso de nossos exemplos de código para estender sua integração S2S atual. Os endpoints de sessão e evento S2S da Singular já suportam a obtenção de parâmetros SKAdNetwork adicionais, tais como:

  • Último valor de conversão
  • Último registro de data e hora de uma chamada para a estrutura SKAdNetwork subjacente
  • Primeiro registo de data e hora de uma chamada para a estrutura SKAdNetwork subjacente

Fluxo de integração

Screen_Shot_2020-09-16_at_18.59.13.png

O diagrama acima ilustra o fluxo de SKAdNetwork para um cliente S2S:

  1. Primeiro, o código na aplicação comunica com um servidor Singular (através de um endpoint dedicado para SKAdNetwork) para obter o valor de conversão mais recente de forma síncrona com base em eventos/sessões/receitas que ocorrem na aplicação e atualizar a estrutura SKAdNetwork com este valor.
  2. Em segundo lugar, a aplicação enriquece os eventos e sessões existentes com dados SKAdNetwork, que serão posteriormente utilizados para validações.
  3. Quando a aplicação terminar de atualizar a estrutura SKAdNetwork com novos valores de conversão e o temporizador SKAdNetwork expirar, o postback SKAdNetwork será enviado para a rede.
  4. A rede encaminhará o postback para a Singular (através da configuração segura ou da configuração normal).
  5. A Singular processará o postback da seguinte forma
    • Validando a sua assinatura
    • Decodificando o valor da conversão com base no modelo de conversão configurado
    • Enriquecer o postback com parâmetros de marketing mais voltados para o exterior, com base na integração com parceiros e no ID de campanha interno da SKAdNetwork
    • Envio dos postbacks descodificados para o BI e para os parceiros

Uma nota importante é que a informação da SKAdNetwork, incluindo instalações e eventos descodificados, estará acessível através de um conjunto diferente de relatórios/APIs/tabelas ETL e postbacks para evitar misturá-la com os conjuntos de dados existentes. Isso é especialmente importante durante as próximas semanas para permitir que você meça e teste SKAdNetwork lado a lado com o seu esforço de campanha existente.

Amostras de código iOS nativo de SKAdNetwork

Interface de SKAdNetwork

Essa interface inclui os seguintes componentes de SKAdNetwork:

Registo em SKAdNetwork:

  • Este método é responsável por registar a sua aplicação em SKAdNetwork. O método subjacente da API da Apple gera uma notificação se o dispositivo tiver dados de atribuição para essa aplicação e inicia um temporizador de 24 horas.
  • O dispositivo envia a notificação de instalação para o ponto de extremidade de postback da rede de anúncios dentro de 0-24 horas após a expiração do temporizador.
  • Observe que, se você atualizar o valor de conversão com um valor maior do que o anterior, ele redefinirá o primeiro timer para um novo intervalo de 24 horas(leia mais aqui).

Gestão e actualizações do valor de conversão:

  • O valor de conversão é calculado com base na atividade pós-instalação de um dispositivo que é capturada pelos métodos abaixo e num modelo de conversão selecionado que pode ser configurado dinamicamente pelo utilizador.
  • Os métodos nesta secção são responsáveis por extrair o próximo valor de conversão do ponto de extremidade da Singular de acordo com o modelo de conversão selecionado e a atividade pós-instalação comunicada (ver documentação acima).
  • Os métodos abaixo atualizam o valor de conversão com base nas seguintes atividades pós-instalação:
      • Sessão: essencial para a medição da retenção de um utilizador com SKAdNetwork
      • Conversion Event: essencial para a medição de eventos de conversão pós-instalação com SKAdNetwork
      • Evento de receita: Crítico para a medição de receita com SKAdNetwork
//  SKANSnippet.h


#import <Foundation/Foundation.h>

@interface
SKANSnippet : NSObject

// Register for SKAdNetwork attribution.

// You should call this method as soon as possible once app is launched

+ (void)registerAppForAdNetworkAttribution;

// To track retention and cohorts you need to call this method after 

// each session. It reports the session details and updates the conversion 

// value due to this session if needed. The conversion value will be 

// updated only if the new value is greater than the previous value.

// The callback passed to the method is optional, you can use it to 

// run code once the conversion value is updated. 

+ (void)updateConversionValueAsync:(void(^)(int, NSError*))handler;

// To track conversion events with SKAdNetwork you need to call this 

// method after each event. It reports the event details and updates the 

// conversion value due to this event if needed.  

// The callback passed to the method is optional, you can use it to 

// run code once the conversion value is updated.

+ (void)updateConversionValueAsync:(NSString*)eventName
  withCompletionHandler:(void(^)(int, NSError*))handler;

// To track revenue with SKAdNetwork you need to call this 

// method before each  revenue event .

// It will update the total revenue, so when you call 'updateConversionValueAsync', 

// the new conversion value will be determined according to the total amount of revenue.

// Note:

// 1. Call this method before calling 'updateConversionValueAsync' to 

// make sure that revenue is updated.

// 2. In case of retrying an event, avoid calling this method

// so the same revenue will not count twice.

+ (void)updateRevenue:(double)amount andCurrency:(NSString*)currency;

// Gets the current conversion value (nil if none)

+ (NSNumber *)getConversionValue;

 @end

Implementação da interface SKAdNetwork

//  SKANSnippet.m


#import "SKANSnippet.h"
#import <StoreKit/SKAdNetwork.h>

#define SESSION_EVENT_NAME @”__SESSION__” 
#define SINGULAR_API_URL @"https://sdk-api-v1.singular.net/api/v1/conversion_value"

// Keys for UserDefaults storage

#define CONVERSION_VALUE_KEY @"skan_conversion_value"
#define FIRST_SKAN_CALL_TIMESTAMP @"skan_first_call_to_skadnetwork_timestamp"
#define LAST_SKAN_CALL_TIMESTAMP @"skan_last_call_to_skadnetwork_timestamp"
#define TOTAL_REVENUE_BY_CURRENCY_KEY @"skan_total_revenue_by_currency"
#define SECONDS_PER_DAY 86400

@implementation
SKANSnippet

+ (void)registerAppForAdNetworkAttribution {
    if ([SKANSnippet getFirstSkanCallTimestamp] != 0) {
        return;
    }

    if (@available(iOS 11.3, *) ) {
        [SKAdNetwork registerAppForAdNetworkAttribution];
        [SKANSnippet setFirstSkanCallTimestamp];
        [SKANSnippet setLastSkanCallTimestamp];
    }
}

+ (void)updateConversionValueAsync:(void(^)(int, NSError*))handler {
    [SKANSnippet updateConversionValueAsync:SESSION_EVENT_NAME withCompletionHandler:handler];
}

+ (void)updateConversionValueAsync:(NSString*)eventName withCompletionHandler:(void(^)(int, NSError*))handler {
    if (@available(iOS 14, *)) {
        if ([SKANSnippet isSkanUpdateWindowOver]) {
            return;
        }
        
        [SKANSnippet getConversionValueFromServer:eventName withCompletionHandler:handler];
    }
}

+ (void)updateRevenue:(double)amount andCurrency:(NSString*)currency {
    if (@available(iOS 14, *)) {
        
        // Update total revenues

        if (amount != 0 && currency) {
            NSMutableDictionary* revenues = [[SKANSnippet getTotalRevenue] mutableCopy];
            NSNumber* currentRevenue = 0;
            
            if ([revenues objectForKey:currency]) {
                currentRevenue = [revenues objectForKey:currency];
            }
            
            currentRevenue = @([currentRevenue floatValue] + amount);
            [revenues setObject:currentRevenue forKey:currency];
            
            [SKANSnippet setTotalRevenue:revenues];
        }
    }
}

+ (NSNumber *)getConversionValue {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    if (![userDefaults objectForKey:CONVERSION_VALUE_KEY]) {
        return nil;
    }
    return @([userDefaults integerForKey:CONVERSION_VALUE_KEY]);
}

+ (void)getConversionValueFromServer:(NSString*)eventName withCompletionHandler:(void(^)(int, NSError*))handler {
    if (!lockObject) {
        lockObject = [NSLock new];
    }
    
    // Making the lock async so it will not freeze the calling thread

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
        
        [lockObject lock];
        
        NSURLComponents *components = [NSURLComponents componentsWithString:SINGULAR_API_URL];
        
        NSString* bundleIdentifier = @"";
        
        components.queryItems = @[
            [NSURLQueryItem queryItemWithName:@"a" value:@""],
            [NSURLQueryItem queryItemWithName:@"i" value:bundleIdentifier],
            [NSURLQueryItem queryItemWithName:@"app_v" value:@""],
            [NSURLQueryItem queryItemWithName:@"n" value:eventName],
            [NSURLQueryItem queryItemWithName:@"p" value:@"iOS"],
            [NSURLQueryItem queryItemWithName:@"idfv" value:@""],
            [NSURLQueryItem queryItemWithName:@"idfa" value:@""],
            [NSURLQueryItem queryItemWithName:@"conversion_value" 
 value:[[SKANSnippet getConversionValue] stringValue]],
            [NSURLQueryItem queryItemWithName:@"total_revenue_by_currency" 
 value:[SKANSnippet dictionaryToJsonString:[SKANSnippet getTotalRevenue]]],
            [NSURLQueryItem queryItemWithName:@"first_call_to_skadnetwork_timestamp" 
 value:[NSString stringWithFormat:@"%ld", [SKANSnippet getFirstSkanCallTimestamp]]],
            [NSURLQueryItem queryItemWithName:@"last_call_to_skadnetwork_timestamp" 
 value:[NSString stringWithFormat:@"%ld", [SKANSnippet getLastSkanCallTimestamp]]],
        ];
        
        [[[NSURLSession sharedSession] dataTaskWithURL:components.URL
                                     completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                [lockObject unlock];
                if (handler) {
                    handler(-1, error);
                }
                return;
            }
            
            NSDictionary* parsedResponse = [SKANSnippet jsonDataToDictionary:data];
            
            if (!parsedResponse) {
                [lockObject unlock];
                if (handler) {
                    handler(-1, [NSError errorWithDomain:bundleIdentifier
                                                    code:0
                                                userInfo:@{NSLocalizedDescriptionKey:@"Failed parsing server response"}]);
                }
                return;
            }
            
            NSNumber *conversionValue = [parsedResponse objectForKey:@"conversion_value"];
            
            if (!conversionValue) {
                [lockObject unlock];
                NSString *status = [parsedResponse objectForKey:@"status"];
                
                if (!status || ![status isEqualToString:@"ok"]) {
                    if (handler) {
                    		NSString *reason = [parsedResponse objectForKey:@"reason"];
                        if (!reason) {
                            reason = @"Got error from server";
                        }
                        
                        handler(-1, [NSError errorWithDomain:bundleIdentifier
                                                        code:0
                                                    userInfo:@{NSLocalizedDescriptionKey:reason}]);
                    }
                }
                return;
            }
            
            NSNumber* currentValue = [SKANSnippet getConversionValue];
            if ([conversionValue intValue] <= [currentValue intValue]) {
                [lockObject unlock];
                return;
            }
            
            [SKANSnippet setConversionValue:[conversionValue intValue]];
            [SKANSnippet setLastSkanCallTimestamp];
            
            if (![SKANSnippet getFirstSkanCallTimestamp]) {
                [SKANSnippet setFirstSkanCallTimestamp];
            }
            
            [lockObject unlock];
            
            if (handler) {
                handler([conversionValue intValue], error);
            }
        }] resume];
    });
}

+ (BOOL)isSkanUpdateWindowOver {        
    NSInteger timeDiff = [SKANSnippet getCurrentUnixTimestamp] - 
      [SKANSnippet getLastSkanCallTimestamp];
    return SECONDS_PER_DAY <= timeDiff;
}

Metadados e métodos utilitários de SKAdNetwork

Esta secção é responsável pela implementação de utilitários que estão a ser utilizados na implementação acima. Note-se que estes utilitários são essenciais para manter e armazenar os metadados necessários para calcular os valores de conversão seguintes.

São implementados os seguintes utilitários:

Guardar valores em UserDefaults (e recuperá-los):

  • Registo de data e hora da primeira chamada para a API SKAdNetwork subjacente
  • Registo de data e hora da última chamada para a API SKAdNetwork subjacente
  • Receita total por moeda

Conversão de valores entre diferentes representações:

  • Dicionário para string JSON
  • JSON para dicionário
+ (NSInteger)getFirstSkanCallTimestamp {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    return [userDefaults integerForKey:FIRST_SKAN_CALL_TIMESTAMP];
}

+ (NSInteger)getLastSkanCallTimestamp {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    return [userDefaults integerForKey:LAST_SKAN_CALL_TIMESTAMP];
}

+ (NSDictionary*)getTotalRevenue {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    if (![userDefaults objectForKey:TOTAL_REVENUE_BY_CURRENCY_KEY]) {
        return [NSDictionary new];
    }
    return [userDefaults objectForKey:TOTAL_REVENUE_BY_CURRENCY_KEY];
}

+ (NSInteger)getCurrentUnixTimestamp {
    return [[NSDate date]timeIntervalSince1970];
}

+ (void)setConversionValue:(int)value {
    if (@available(iOS 14.0, *)) {
        if (value <= [[SKANSnippet getConversionValue] intValue]) {
            return;
        }
        
        [SKAdNetwork updateConversionValue:value];
        
        NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
        [userDefaults setInteger:value forKey:CONVERSION_VALUE_KEY];
        [userDefaults synchronize];
    }
}

+ (void)setFirstSkanCallTimestamp {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults setInteger:[SKANSnippet getCurrentUnixTimestamp] 
      forKey:FIRST_SKAN_CALL_TIMESTAMP];
    [userDefaults synchronize];
}

+ (void)setLastSkanCallTimestamp {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults setInteger:[SKANSnippet getCurrentUnixTimestamp] 
      forKey:LAST_SKAN_CALL_TIMESTAMP];
    [userDefaults synchronize];
}

+ (void)setTotalRevenue:(NSDictionary *)values {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults setObject:values forKey:TOTAL_REVENUE_BY_CURRENCY_KEY];
    [userDefaults synchronize];
}

+ (NSString*)dictionaryToJSONString:(NSDictionary*)dictionary {
    if (!dictionary || [dictionary count] == 0){
        return @"{}";
    }
    
    NSError *error;
    NSData *JSONData = [NSJSONSerialization dataWithJSONObject:dictionary
      options:0
      error:&error];
    if (error || !JSONData) {
        return @"{}";
    }
    
    return [[NSString alloc] initWithData:JSONData 
      encoding:NSUTF8StringEncoding];
}

+ (NSDictionary*)JSONDataToDictionary:(NSData*)JSONData {
    if (!JSONData) {
        return nil;
    }

    NSError * error;
    NSDictionary * parsedData = [NSJSONSerialization 
      JSONObjectWithData:JSONData
      options:kNilOptions error:&error];
      
    if (error || !parsedData) {
       return nil;
    }
    return parsedData;
}

@end

Atualização da integração S2S - Implementação (opcional)

Atualize sua integração S2S com os seguintes metadados da SKAdNetwork (esses metadados devem ser encaminhados em todas as sessões e eventos reportados à Singular para validação da implementação da SKAdNetwork):

  • skan_conversion_value - O valor de conversão mais recente
  • skan_first_call_timestamp - Carimbo de data/hora Unix da primeira chamada à API SKAdNetwork subjacente
  • skan_last_call_timestamp - Carimbo de data/hora Unix da última chamada à API SKAdNetwork subjacente

O seguinte trecho de código mostra como extrair esses valores:

NSDictionary *values = @{
    @"skan_conversion_value":
      [[SKANSnippet getConversionValue] stringValue]],
    @"skan_first_call_timestamp": 
      [NSString stringWithFormat:@"%ld", [SKANSnippet getFirstSkanCallTimestamp]],
    @"skan_last_call_timestamp": 
      [NSString stringWithFormat:@"%ld", [SKANSnippet getLastSkanCallTimestamp]]
};

Assim que enviar estes parâmetros para o seu servidor, pode reencaminhá-los através dos nossos endpoints API de servidor para servidor. Para saber mais, pesquise estes parâmetros na nossa referência da API server-to-server.

Exemplo de fluxo do ciclo de vida da aplicação

// On app launch

[SKANSnippet registerAppForAdNetworkAttribution];

// After each session is handled

[SKANSnippet updateConversionValueAsync:handler];

// After handling non-revenue events

[SKANSnippet updateConversionValueAsync:@"event_name" 
  withCompletionHandler:handler];

// After handling revenue events

[SKANSnippet updateRevenue:15.53 andCurrency:@"KRW"];
[SKANSnippet updateConversionValueAsync:@"revenue_event_name" withCompletionHandler:handler];

Registo de alterações de código

  • 1 de outubro de 2020
    • Correção [IMPORTANTE]: o mecanismo de bloqueio em `getConversionValueFromServer`não foi iniciado corretamente
    • Melhorado: retorno do motivo de falha da resposta do nosso servidor
  • 2020/09/23
    • Correção [IMPORTANTE]: `setConversionValue` chama agora `updateConversionValue`
    • Melhorado: tratamento de erros ao recuperar o valor de conversão mais recente do servidor
  • 2020/09/15
    • Alterado [IMPORTANTE]: Se o valor de conversão não for definido, o valor predefinido devolvido por `getConversionValue` é 0 em vez de nulo
    • Melhorado: Relatório e tratamento de receitas
    • Melhorado: Recuperação do próximo valor de conversão de forma assíncrona

Testar a sua implementação

Gerenciamento de conversões

Para simular o fluxo da aplicação e testar a implementação, substitua o endpoint de produção do gerenciamento de conversões(SINGULAR_API_URL) pelo seguinte endpoint de teste -

Um fluxo de teste sugerido:

  • Gerar 3 eventos diferentes na sua aplicação.
    • Se tudo estiver implementado corretamente -
      • Após cada evento, updateConversionValueAsync deve ser chamado (certifique-se de que o valor de conversão padrão seja inicializado como 0).
      • O ponto de extremidade de teste recebe o valor de conversão atual do aplicativo e retorna o próximo valor.
  • Registre o valor de conversão retornado para ver se tudo está funcionando conforme o esperado.
    • O ponto de extremidade de teste é implementado para retornar valores entre 0-2.
    • Por conseguinte, após a terceira chamada, devolverá uma cadeia vazia e o último valor será utilizado como o valor final de conversão SKAdNetwork.