Todos os produtos
Search
Central de documentação

Simple Log Service:Get started with the iOS SDK

Última atualização: Jul 03, 2026

O Simple Log Service SDK para iOS coleta e envia dados de log de aplicativos iOS para o Simple Log Service. O SDK oferece suporte a envio assíncrono de logs, upload retomável, configuração dinâmica de parâmetros e inicialização de múltiplas instâncias.

Pré-requisitos

Instale o iOS SDK. Para mais informações, consulte Instalar o iOS SDK.

Início rápido

Inicialize o SDK e chame o método addLog para enviar logs.

Importante
  • O iOS SDK permite a inicialização de várias instâncias. Use as instâncias de LogProducerConfig e LogProducerClient em pares.

  • Ao enviar logs para o Simple Log Service, use o par de AccessKey de uma conta Alibaba Cloud ou de um usuário do Resource Access Management (RAM) para autenticação e proteção contra adulteração. Armazenar um par de AccessKey no aplicativo móvel representa um risco de segurança. Para evitar esse risco, recomendamos usar um serviço para transferir logs diretamente dos dispositivos móveis e configurar o par de AccessKey nesse serviço. Para mais informações, consulte Criar um serviço para fazer upload de logs de dispositivos móveis para o Simple Log Service.

@interface ProducerExampleController ()
// Save the LogProducerConfig and LogProducerClient instances globally.
@property(nonatomic, strong) LogProducerConfig *config;
@property(nonatomic, strong) LogProducerClient *client;
@end

@implementation ProducerExampleController

// The callback function is optional. If you do not need to know whether a log is sent, you do not need to register a callback function.
// To dynamically configure an AccessKey pair, set the callback function and update the AccessKey pair when the callback function is invoked.
static void _on_log_send_done(const char * config_name, log_producer_result result, size_t log_bytes, size_t compressed_bytes, const char * req_id, const char * message, const unsigned char * raw_buffer, void * userparams) {
    if (result == LOG_PRODUCER_OK) {
        NSString *success = [NSString stringWithFormat:@"send success, config : %s, result : %d, log bytes : %d, compressed bytes : %d, request id : %s", config_name, (result), (int)log_bytes, (int)compressed_bytes, req_id];
        SLSLogV("%@", success);
    } else {
        NSString *fail = [NSString stringWithFormat:@"send fail   , config : %s, result : %d, log bytes : %d, compressed bytes : %d, request id : %s, error message : %s", config_name, (result), (int)log_bytes, (int)compressed_bytes, req_id, message];
        SLSLogV("%@", fail);
    }
}

- (void) initLogProducer {
    // The endpoint of Simple Log Service. The endpoint must start with https:// or http://.
    NSString *endpoint = @"your endpoint";
    NSString *project = @"your project";
    NSString *logstore = @"your logstore";

    _config = [[LogProducerConfig alloc] initWithEndpoint:endpoint
                                                  project:project
                                                 logstore:logstore
    ];

    // Set the log topic.
    [_config SetTopic:@"example_topic"];
    // Set tags. The tags are attached to each log.
    [_config AddTag:@"example" value:@"example_tag"];
    // Specifies whether to discard expired logs. A value of 0 indicates that expired logs are not discarded and the log time is updated to the current time. A value of 1 indicates that expired logs are discarded. Default value: 0.
    [_config SetDropDelayLog:1];
    // Specifies whether to discard logs for which authentication failed. A value of 0 indicates that the logs are not discarded. A value of 1 indicates that the logs are discarded. Default value: 0.
    [_config SetDropUnauthorizedLog:0];    

    // If you want to check whether a log is sent, pass a callback function as the second parameter.
    _client = [[LogProducerClient alloc] initWithLogProducerConfig:_config callback:_on_log_send_done];
}

// Request the AccessKey pair information.
- (void) requestAccessKey {
    // We recommend that you first use the service for direct log transfer from mobile devices to configure the AccessKey pair information.
    // ...

    // After you obtain the AccessKey pair information, update the information.
    [self updateAccessKey:accessKeyId accessKeySecret:accessKeySecret securityToken:securityToken];
}

// Update the AccessKey pair information.
- (void) updateAccessKey:(NSString *)accessKeyId accessKeySecret:(NSString *)accessKeySecret securityToken:(NSString *)securityToken {
    
    // If you obtain an AccessKey pair using Security Token Service (STS), the AccessKey pair contains a security token. In this case, you must update the AccessKey pair in the following way.
    if (securityToken.length > 0) {
        if (accessKeyId.length > 0 && accessKeySecret.length > 0) {
            [_config ResetSecurityToken:accessKeyId
                        accessKeySecret:accessKeySecret
                          securityToken:securityToken
            ];
        }
    } else {
        // If you do not obtain an AccessKey pair using STS, update the AccessKey pair in the following way.
        if (accessKeyId.length > 0 && accessKeySecret.length > 0) {
            [_config setAccessKeyId: accessKeyId];
            [_config setAccessKeySecret: accessKeySecret];
        }
    }
}
// Report a log.
- (void) addLog {
    Log *log = [Log log];
    // You can adjust the fields to report as needed.
    [log putContent:@"content_key_1" intValue:123456];
    [log putContent:@"content_key_2" floatValue:23.34f];
    [log putContent:@"content_key_3" value:@"Chinese characters"];
    
    [_client AddLog:log];
}
@end

Uso avançado

Configurar parâmetros dinamicamente

O iOS SDK permite configurar dinamicamente parâmetros como ProjectName, Logstore, Endpoint e AccessKey. Para obter detalhes sobre como conseguir um endpoint, consulte Endpoints. Para saber como obter um par de AccessKey, consulte Par de AccessKey.

  • Configure dinamicamente Endpoint, ProjectName e Logstore.

    // You can configure the Endpoint, ProjectName, and Logstore parameters independently or together.
    // Update the endpoint.
    [_config setEndpoint:@"your new-endpoint"];
    // Update ProjectName.
    [_config setProject:@"your new-project"];
    // Update the Logstore.
    [_config setLogstore:@"your new-logstore"];
  • Configure dinamicamente um par de AccessKey.

    Ao configurar dinamicamente um par de AccessKey, recomendamos usá-lo em conjunto com uma função de callback.

    // If you have initialized the callback function when you initialize LogProducerClient, you can ignore the following code.
    static void _on_log_send_done(const char * config_name, log_producer_result result, size_t log_bytes, size_t compressed_bytes, const char * req_id, const char * message, const unsigned char * raw_buffer, void * userparams) {
        if (LOG_PRODUCER_SEND_UNAUTHORIZED == result || LOG_PRODUCER_PARAMETERS_INVALID) {
            [selfClzz requestAccessKey]; // A reference to the current class instance, captured for use within the C-style callback function.
        }
    }
    
    // If you want to check whether a log is sent, pass a callback function as the second parameter.
    _client = [[LogProducerClient alloc] initWithLogProducerConfig:_config callback:_on_log_send_done];
    
    // Request the AccessKey pair information.
    - (void) requestAccessKey {
        // We recommend that you first use the service for direct log transfer from mobile devices to configure the AccessKey pair information.
        // ...
    
        // After you obtain the AccessKey pair information, update the information.
        [self updateAccessKey:accessKeyId accessKeySecret:accessKeySecret securityToken:securityToken];
    }
    
    // Update the AccessKey pair information.
    - (void) updateAccessKey:(NSString *)accessKeyId accessKeySecret:(NSString *)accessKeySecret securityToken:(NSString *)securityToken {
        
        // If you obtain an AccessKey pair using STS, the AccessKey pair contains a security token. In this case, you must update the AccessKey pair in the following way.
        if (securityToken.length > 0) {
            if (accessKeyId.length > 0 && accessKeySecret.length > 0) {
                [_config ResetSecurityToken:accessKeyId
                            accessKeySecret:accessKeySecret
                              securityToken:securityToken
                ];
            }
        } else {
            // If you do not obtain an AccessKey pair using STS, update the AccessKey pair in the following way.
            if (accessKeyId.length > 0 && accessKeySecret.length > 0) {
                [_config setAccessKeyId: accessKeyId];
                [_config setAccessKeySecret: accessKeySecret];
            }
        }
    }
  • Defina dinamicamente source, topic e tag.

    Importante

    Essas configurações têm escopo global e afetam todos os logs subsequentes, inclusive aqueles que já estão no buffer de nova tentativa. Caso sua lógica de negócio exija rastrear tipos específicos de log com esses parâmetros, esse comportamento global pode gerar resultados inesperados. Adicione um campo personalizado a cada log para identificar seu tipo.

    // Set the log topic.
    [_config SetTopic:@"your new-topic"];
    // Set the log source.
    [_config SetSource:@"your new-source"];
    // Set tags. The tags are attached to each log.
    [_config AddTag:@"test" value:@"your new-tag"];

Upload retomável

O iOS SDK oferece suporte a upload retomável. Quando esse recurso está ativado, os logs enviados pelo método addLog são primeiro persistidos em um arquivo de log binário (binlog) local. Os dados locais só são excluídos após o envio bem-sucedido dos logs. Isso garante a semântica de pelo menos uma vez (at-least-once) para uploads de log.

Para implementar o upload retomável, adicione o código a seguir durante a inicialização do SDK.

Importante
  • Ao inicializar múltiplas instâncias de LogProducerConfig, forneça um caminho de arquivo exclusivo ao método setPersistentFilePath da classe LogProducerConfig para cada instância.

  • Se o aplicativo usar vários processos e o upload retomável estiver ativado, inicialize o SDK apenas no processo principal. Caso os processos filhos também precisem coletar dados, garanta que o caminho fornecido ao método SetPersistentFilePath seja único. Do contrário, os dados de log podem ficar desordenados ou ser perdidos.

  • Fique atento a possíveis problemas causados por múltiplas threads na inicialização repetida do LogProducerConfig.

- (void) initLogProducer {
    // A value of 1 enables resumable upload. A value of 0 disables this feature. Default value: 0.
    [_config SetPersistent:1];
    
    // The name of the persistence file. Make sure that the folder in which the file is stored is created.
    NSArray  *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *Path = [[paths lastObject] stringByAppendingString:@"/log.dat"];
    [_config SetPersistentFilePath:Path];
    // The number of persistent files that can be rolled over. Set this parameter to 10.
    [_config SetPersistentMaxFileCount:10];
    // The size of each persistent file in bytes. The value is calculated using the formula: N × 1024 × 1024. Set N to a value from 1 to 10.
    [_config SetPersistentMaxFileSize:N*1024*1024];
    // The maximum number of logs that can be cached locally. Do not set this parameter to a value greater than 1,048,576. Default value: 65,536.
    [_config SetPersistentMaxLogCount:65536];
}

Referência de configuração

A classe LogProducerConfig fornece todos os parâmetros de configuração. A tabela a seguir descreve esses parâmetros.

Parâmetro

Tipo de dado

Descrição

SetTopic

String

Define o valor do campo topic. O valor padrão é uma string vazia.

AddTag

String

Define uma tag no formato tag:xxxx. O valor padrão é uma string vazia.

SetSource

String

Define o valor do campo source. Valor padrão: iOS.

SetPacketLogBytes

Int

Tamanho máximo de um pacote de logs em cache. Se o limite for excedido, os logs serão enviados imediatamente.

Valores válidos: 1 a 5.242.880. Valor padrão: 1024 × 1024. Unidade: bytes.

SetPacketLogCount

Int

Número máximo de logs em um pacote em cache. Ao ultrapassar esse limite, o envio ocorre imediatamente.

Valores válidos: 1 a 4.096. Valor padrão: 1024.

SetPacketTimeout

Int

Tempo limite para envio de logs em cache. Caso o tempo expire, os logs são enviados de imediato.

Valor padrão: 3.000. Unidade: milissegundos.

SetMaxBufferLimit

Int

Memória máxima que uma única instância do Producer Client pode utilizar. Se o limite for atingido, a interface add_log retorna falha imediatamente.

Valor padrão: 64 × 1024 × 1024.

SetPersistent

Int

Indica se o upload retomável deve ser ativado.

  • 1: ativa o recurso.

  • 0 (padrão): desativa o recurso.

SetPersistentFilePath

String

Nome do arquivo de persistência. Certifique-se de que a pasta onde o arquivo será armazenado já exista. Ao configurar múltiplas instâncias de LogProducerConfig, garanta que o nome seja único.

O valor padrão é vazio.

SetPersistentForceFlush

Int

Determina se o recurso de force flush deve ser ativado a cada chamada de AddLog. Quando ativado, o SDK grava imediatamente o buffer de logs no arquivo persistente em disco.

  • 1: ativa o recurso. Isso afeta o desempenho. Ative com cautela.

  • 0 (padrão): desativa o recurso.

Ative este recurso em cenários que exigem alta confiabilidade.

SetPersistentMaxFileCount

Int

Quantidade de arquivos persistentes para rotação. O valor 0 indica que não há rotação (uso de arquivo único). Valor recomendado: 10. Padrão: 0.

SetPersistentMaxFileSize

Int

Tamanho de cada arquivo persistente em bytes. O cálculo segue a fórmula: N × 1024 × 1024. Defina N com um valor entre 1 e 10.

SetPersistentMaxLogCount

Int

Limite máximo de logs que podem ser armazenados em cache localmente. Não defina este parâmetro com valor superior a 1.048.576. Valor padrão: 65.536.

SetConnectTimeoutSec

Int

Tempo limite de conexão. Valor padrão: 10. Unidade: segundos.

SetSendTimeoutSec

Int

Tempo limite para envio de logs. Valor padrão: 15. Unidade: segundos.

SetDestroyFlusherWaitSec

Int

Tempo máximo de espera para destruição da thread flusher. Valor padrão: 1. Unidade: segundo.

SetDestroySenderWaitSec

Int

Tempo máximo de espera para destruição do pool de threads sender. Valor padrão: 1. Unidade: segundo.

SetCompressType

Int

Tipo de compressão para upload de dados.

  • 0: sem compressão.

  • 1 (padrão): compressão LZ4.

SetNtpTimeOffset

Int

Diferença entre a hora do dispositivo e a hora padrão. O cálculo segue a fórmula: Hora padrão - Hora do dispositivo. Essa diferença geralmente ocorre porque a hora do dispositivo cliente não está sincronizada. Valor padrão: 0. Unidade: segundos.

SetMaxLogDelayTime

Int

Diferença entre a hora do log e a hora local. Se a diferença exceder este valor, o SDK processará o log conforme a opção setDropDelayLog. Unidade: segundos. Valor padrão: 7 × 24 × 3600, equivalente a 7 dias.

SetDropDelayLog

Int

Define se logs expirados que ultrapassam o valor de setMaxLogDelayTime devem ser descartados.

  • 0: não descarta os logs e atualiza a hora do log para a hora atual.

  • 1 (padrão): descarta os logs.

SetDropUnauthorizedLog

Int

Indica se logs com falha de autenticação devem ser descartados.

  • 0 (padrão): não descarta os logs.

  • 1: descarta os logs.

Códigos de erro

Todos os códigos de erro estão definidos em log_producer_result. Consulte a tabela abaixo para mais detalhes.

Código de erro

Valor

Descrição

Solução

LOG_PRODUCER_OK

0

Sucesso.

N/A.

LOG_PRODUCER_INVALID

1

O SDK foi destruído ou é inválido.

  1. Verifique se o SDK foi inicializado corretamente.

  2. Confira se o método destroy() foi chamado.

LOG_PRODUCER_WRITE_ERROR

2

Ocorreu um erro de gravação de dados. A causa pode ser o tráfego de gravação do projeto ter atingido o limite superior.

Ajuste o limite superior de tráfego de gravação do projeto. Para mais informações, consulte Ajustar cotas de recursos.

LOG_PRODUCER_DROP_ERROR

3

O cache de disco ou memória está cheio, impedindo a gravação de logs.

Ajuste os valores dos parâmetros maxBufferLimit, persistentMaxLogCount e persistentMaxFileSize e tente novamente.

LOG_PRODUCER_SEND_NETWORK_ERROR

4

Ocorreu um erro de rede.

Verifique as configurações de Endpoint, Project e Logstore.

LOG_PRODUCER_SEND_QUOTA_ERROR

5

O tráfego de gravação do projeto atingiu o limite superior.

Ajuste o limite superior de tráfego de gravação do projeto. Para mais informações, consulte Ajustar cotas de recursos.

LOG_PRODUCER_SEND_UNAUTHORIZED

6

O par de AccessKey expirou ou é inválido, ou a política de acesso está configurada incorretamente.

Verifique o par de AccessKey.

Um usuário RAM precisa ter permissões para gerenciar recursos do Simple Log Service. Para mais informações, consulte Conceder permissões a um usuário RAM.

LOG_PRODUCER_SEND_SERVER_ERROR

7

Ocorreu um erro de serviço.

Envie um ticket para entrar em contato com o suporte técnico.

LOG_PRODUCER_SEND_DISCARD_ERROR

8

Dados foram descartados. Geralmente, isso ocorre porque a hora do dispositivo não está sincronizada com a hora do servidor.

O SDK reenvia os dados automaticamente.

LOG_PRODUCER_SEND_TIME_ERROR

9

A hora não está sincronizada com a hora do servidor.

O SDK corrige esse problema automaticamente.

LOG_PRODUCER_SEND_EXIT_BUFFERED

10

Os dados em cache não foram enviados quando o SDK foi destruído.

Ative o upload retomável para evitar perda de dados.

LOG_PRODUCER_PARAMETERS_INVALID

11

Ocorreu um erro nos parâmetros de inicialização do SDK.

Verifique as configurações de parâmetros como AccessKey, Endpoint, Project e Logstore.

LOG_PRODUCER_PERSISTENT_ERROR

99

Falha ao gravar dados em cache no disco.

1. Verifique se o caminho do arquivo de cache está configurado corretamente.

2. Confira se o arquivo de cache está cheio.

3. Verifique se o disco do sistema tem espaço suficiente.

Perguntas frequentes

Por que existem logs duplicados?

O iOS SDK envia logs de forma assíncrona. Devido às condições da rede, um log pode falhar no envio e ser reenviado posteriormente. O SDK considera que um log foi enviado com sucesso apenas quando recebe um código de status 200. Isso pode resultar em logs duplicados. Utilize instruções SQL durante consultas e análises para remover dados duplicados.

Se você observar uma alta taxa de duplicação de logs, verifique se há erros na inicialização do SDK. A lista a seguir descreve causas comuns e suas soluções.

  • Configuração incorreta para upload retomável

    Verifique se o caminho de arquivo fornecido ao método SetPersistentFilePath é globalmente único.

  • Inicialização repetida do SDK

    • Uma causa comum de inicialização repetida do SDK é a implementação incorreta da instância singleton ou a falta de uso do padrão singleton na inicialização. Inicialize o SDK conforme o exemplo a seguir.

      // AliyunLogHelper.h
      @interface AliyunLogHelper : NSObject
      + (instancetype)sharedInstance;
      - (void) addLog:(Log *)log;
      @end
      
      // AliyunLogHelper.m
      @interface AliyunLogHelper ()
      @property(nonatomic, strong) LogProducerConfig *config;
      @property(nonatomic, strong) LogProducerClient *client;
      @end
      
      @implementation AliyunLogHelper
      
      + (instancetype)sharedInstance {
          static AliyunLogHelper *sharedInstance = nil;
          static dispatch_once_t onceToken;
          dispatch_once(&onceToken, ^{
              sharedInstance = [[self alloc] init];
          });
          return sharedInstance;
      }
      
      - (instancetype)init {
          self = [super init];
          if (self) {
              [self initLogProducer];
          }
          return self;
      }
      
      - (void) initLogProducer {
          // Replace the following code with your initialization code.
          _config = [[LogProducerConfig alloc] initWithEndpoint:@""
                                                        project:@""
                                                       logstore:@""
                                                    accessKeyID:@""
                                                accessKeySecret:@""
                                                  securityToken:@""
          ];
      
          _client = [[LogProducerClient alloc] initWithLogProducerConfig:_config callback:_on_log_send_done];
      }
      
      - (void) addLog:(Log *)log {
          if (nil == log) {
              return;
          }
      
          [_client AddLog:log];
      }
      
      @end
    • Outra causa de inicialização repetida do SDK é o uso de múltiplos processos. Inicialize o SDK apenas no processo principal. Se for indispensável inicializar o SDK em processos diferentes, forneça um valor exclusivo para SetPersistentFilePath em cada processo.

  • Otimização de configuração para ambientes com rede instável

    Caso seu aplicativo seja utilizado em um ambiente com conexão de rede instável, recomendamos otimizar os parâmetros de configuração do SDK conforme o exemplo abaixo.

    // Initialize the SDK.
    - (void) initProducer() {
        // Adjust the timeout periods for HTTP connections and sending to reduce the log duplication rate.
        // You can adjust the specific timeout periods as needed.
        [_config SetConnectTimeoutSec:20];
        [_config SetSendTimeoutSec:20];
      
        // Other initialization parameters.
        // ...
    }

O que fazer se houver perda de logs?

O envio de logs é um processo assíncrono. Se o aplicativo for fechado antes que os logs sejam enviados, eles poderão ser perdidos. Ative o upload retomável. Para mais informações, consulte Upload retomável.

O que fazer se houver atraso no relatório de logs?

O SDK envia logs de forma assíncrona. Os logs podem não ser enviados imediatamente devido ao ambiente de rede ou a cenários específicos do aplicativo. Se o atraso no envio ocorrer apenas em alguns dispositivos, isso é normal. Caso contrário, solucione o problema usando os códigos de erro na tabela a seguir.

Código de erro

Descrição

LOG_PRODUCER_SEND_NETWORK_ERROR

Verifique se os parâmetros Endpoint, Project e Logstore estão configurados corretamente.

LOG_PRODUCER_SEND_UNAUTHORIZED

Confira se o par de AccessKey expirou ou é válido, ou se a política de acesso está configurada incorretamente.

LOG_PRODUCER_SEND_QUOTA_ERROR

O tráfego de gravação do projeto atingiu o limite superior. Ajuste o limite de tráfego de gravação do projeto. Para mais informações, consulte Ajustar cotas de recursos.

O iOS SDK oferece suporte a pré-resolução de DNS e políticas de cache?

Sim. O exemplo a seguir mostra como usar o iOS SDK com o HTTPDNS SDK para implementar pré-resolução de DNS e políticas de cache.

Nota: Se o endpoint do Simple Log Service utilizar um nome de domínio HTTPS, consulte Soluções para cenários HTTPS e SNI.
  1. Implemente uma NSURLProtocol personalizada.

    #import <Foundation/Foundation.h>
    #import "HttpDnsNSURLProtocolImpl.h"
    #import <arpa/inet.h>
    #import <zlib.h>
    #import <objc/runtime.h>
    
    static NSString *const hasBeenInterceptedCustomLabelKey = @"HttpDnsHttpMessagePropertyKey";
    static NSString *const kAnchorAlreadyAdded = @"AnchorAlreadyAdded";
    
    @interface HttpDnsNSURLProtocolImpl () <NSStreamDelegate>
    
    @property (strong, readwrite, nonatomic) NSMutableURLRequest *curRequest;
    @property (strong, readwrite, nonatomic) NSRunLoop *curRunLoop;
    @property (strong, readwrite, nonatomic) NSInputStream *inputStream;
    @property (nonatomic, assign) BOOL responseIsHandle;
    @property (assign, nonatomic) z_stream gzipStream;
    
    @end
    
    @implementation HttpDnsNSURLProtocolImpl
    
    - (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(nullable NSCachedURLResponse *)cachedResponse client:(nullable id <NSURLProtocolClient>)client {
      self = [super initWithRequest:request cachedResponse:cachedResponse client:client];
      if (self) {
        _gzipStream.zalloc = Z_NULL;
        _gzipStream.zfree = Z_NULL;
        if (inflateInit2(&_gzipStream, 16 + MAX_WBITS) != Z_OK) {
          [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"gzip initialize fail" code:-1 userInfo:nil]];
        }
      }
      return self;
    }
    
    /**
     *  Specifies whether to intercept and process the specified request.
     *
     *  @param request The specified request.
     *  @return Returns YES to intercept and process the request, or NO to not intercept the request.
     */
    + (BOOL)canInitWithRequest:(NSURLRequest *)request {
      if([[request.URL absoluteString] isEqual:@"about:blank"]) {
        return NO;
      }
    
      // Prevents infinite loops. A request may be re-initiated during interception. If this is not handled, an infinite loop occurs.
      if ([NSURLProtocol propertyForKey:hasBeenInterceptedCustomLabelKey inRequest:request]) {
        return NO;
      }
    
      NSString * url = request.URL.absoluteString;
      NSString * domain = request.URL.host;
    
      // Intercept only HTTPS requests.
      if (![url hasPrefix:@"https"]) {
        return NO;
      }
    
      // You can add more conditions as needed, such as configuring a host array to intercept only the hosts in the array.
    
      // Intercept only requests whose hosts are replaced with IP addresses.
      if (![self isPlainIpAddress:domain]) {
        return NO;
      }
      return YES;
    }
    
    + (BOOL)isPlainIpAddress:(NSString *)hostStr {
      if (!hostStr) {
        return NO;
      }
    
      // Checks whether the address is an IPv4 address.
      const char *utf8 = [hostStr UTF8String];
      int success = 0;
      struct in_addr dst;
      success = inet_pton(AF_INET, utf8, &dst);
      if (success == 1) {
        return YES;
      }
    
      // Checks whether the address is an IPv6 address.
      struct in6_addr dst6;
      success = inet_pton(AF_INET6, utf8, &dst6);
      if (success == 1) {
        return YES;
      }
    
      return NO;
    }
    
    // To redirect the request or add headers, perform the operations in this method.
    + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
      return request;
    }
    
    // Starts to load the request.
    - (void)startLoading {
      NSMutableURLRequest *request = [self.request mutableCopy];
      // Indicates that the request has been processed. This prevents infinite loops.
      [NSURLProtocol setProperty:@(YES) forKey:hasBeenInterceptedCustomLabelKey inRequest:request];
      self.curRequest = [self createNewRequest:request];
      [self startRequest];
    }
    
    - (NSString *)cookieForURL:(NSURL *)URL {
      NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
      NSMutableArray *cookieList = [NSMutableArray array];
      for (NSHTTPCookie *cookie in [cookieStorage cookies]) {
        if (![self p_checkCookie:cookie URL:URL]) {
          continue;
        }
        [cookieList addObject:cookie];
      }
    
      if (cookieList.count > 0) {
        NSDictionary *cookieDic = [NSHTTPCookie requestHeaderFieldsWithCookies:cookieList];
        if ([cookieDic objectForKey:@"Cookie"]) {
          return cookieDic[@"Cookie"];
        }
      }
      return nil;
    }
    
    - (BOOL)p_checkCookie:(NSHTTPCookie *)cookie URL:(NSURL *)URL {
      if (cookie.domain.length <= 0 || URL.host.length <= 0) {
        return NO;
      }
      if ([URL.host containsString:cookie.domain]) {
        return YES;
      }
      return NO;
    }
    
    - (NSMutableURLRequest *)createNewRequest:(NSURLRequest*)request {
      NSURL* originUrl = request.URL;
      NSString *cookie = [self cookieForURL:originUrl];
    
      NSMutableURLRequest* mutableRequest = [request copy];
      [mutableRequest setValue:cookie forHTTPHeaderField:@"Cookie"];
    
      return [mutableRequest copy];
    }
    
    /**
     * Cancels the request.
     */
    - (void)stopLoading {
      if (_inputStream.streamStatus == NSStreamStatusOpen) {
        [self closeStream:_inputStream];
      }
      [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"stop loading" code:-1 userInfo:nil]];
    }
    
    /**
     * Forwards the request using CFHTTPMessage.
     */
    - (void)startRequest {
      // The header information of the original request.
      NSDictionary *headFields = _curRequest.allHTTPHeaderFields;
      CFStringRef url = (__bridge CFStringRef) [_curRequest.URL absoluteString];
      CFURLRef requestURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL);
    
      // The method of the original request, such as GET or POST.
      CFStringRef requestMethod = (__bridge_retained CFStringRef) _curRequest.HTTPMethod;
    
      // Creates a CFHTTPMessageRef object based on the URL, method, and version of the request.
      CFHTTPMessageRef cfrequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, requestURL, kCFHTTPVersion1_1);
    
      // Adds the data attached to the HTTP POST request.
      CFStringRef requestBody = CFSTR("");
      CFDataRef bodyData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, requestBody, kCFStringEncodingUTF8, 0);
    
      if (_curRequest.HTTPBody) {
        bodyData = (__bridge_retained CFDataRef) _curRequest.HTTPBody;
      }  else if (_curRequest.HTTPBodyStream) {
        NSData *data = [self dataWithInputStream:_curRequest.HTTPBodyStream];
        NSString *strBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"originStrBody: %@", strBody);
    
        CFDataRef body = (__bridge_retained CFDataRef) data;
        CFHTTPMessageSetBody(cfrequest, body);
        CFRelease(body);
      } else {
        CFHTTPMessageSetBody(cfrequest, bodyData);
      }
    
      // Copies the header information of the original request.
      for (NSString* header in headFields) {
        CFStringRef requestHeader = (__bridge CFStringRef) header;
        CFStringRef requestHeaderValue = (__bridge CFStringRef) [headFields valueForKey:header];
        CFHTTPMessageSetHeaderFieldValue(cfrequest, requestHeader, requestHeaderValue);
      }
    
      // Creates an input stream for the CFHTTPMessage object.
      #pragma clang diagnostic push
      #pragma clang diagnostic ignored "-Wdeprecated-declarations"
      CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, cfrequest);
      #pragma clang diagnostic pop
      self.inputStream = (__bridge_transfer NSInputStream *) readStream;
    
      // Sets the Server Name Indication (SNI) host information. This is a key step.
      NSString *host = [_curRequest.allHTTPHeaderFields objectForKey:@"host"];
      if (!host) {
        host = _curRequest.URL.host;
      }
    
      [_inputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
      NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys: host, (__bridge id) kCFStreamSSLPeerName, nil];
      [_inputStream setProperty:sslProperties forKey:(__bridge_transfer NSString *) kCFStreamPropertySSLSettings];
      [_inputStream setDelegate:self];
    
      if (!_curRunLoop) {
        // Saves the runloop of the current thread. This is critical for redirected requests.
        self.curRunLoop = [NSRunLoop currentRunLoop];
      }
      // Adds the request to the event queue of the current runloop.
      [_inputStream scheduleInRunLoop:_curRunLoop forMode:NSRunLoopCommonModes];
      [_inputStream open];
    
      CFRelease(cfrequest);
      CFRelease(requestURL);
      cfrequest = NULL;
      CFRelease(bodyData);
      CFRelease(requestBody);
      CFRelease(requestMethod);
    }
    
    - (NSData*)dataWithInputStream:(NSInputStream*)stream {
      NSMutableData *data = [NSMutableData data];
      [stream open];
      NSInteger result;
      uint8_t buffer[1024];
    
      while ((result = [stream read:buffer maxLength:1024]) != 0) {
        if (result > 0) {
          // buffer contains result bytes of data to be handled
          [data appendBytes:buffer length:result];
        } else if (result < 0) {
          // The stream had an error. You can get an NSError object using [iStream streamError]
          data = nil;
          break;
        }
      }
      [stream close];
      return data;
    }
    
    #pragma mark - NSStreamDelegate
    /**
     * The callback function after the input stream receives the complete header.
     */
    - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
      if (eventCode == NSStreamEventHasBytesAvailable) {
        CFReadStreamRef readStream = (__bridge_retained CFReadStreamRef) aStream;
        #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Wdeprecated-declarations"
        CFHTTPMessageRef message = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
        #pragma clang diagnostic pop
        if (CFHTTPMessageIsHeaderComplete(message)) {
          NSInputStream *inputstream = (NSInputStream *) aStream;
          NSNumber *alreadyAdded = objc_getAssociatedObject(aStream, (__bridge const void *)(kAnchorAlreadyAdded));
          NSDictionary *headDict = (__bridge NSDictionary *) (CFHTTPMessageCopyAllHeaderFields(message));
    
          if (!alreadyAdded || ![alreadyAdded boolValue]) {
            objc_setAssociatedObject(aStream, (__bridge const void *)(kAnchorAlreadyAdded), [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_COPY);
            // Notifies the client that the response is received. This notification is sent only once.
    
            CFStringRef httpVersion = CFHTTPMessageCopyVersion(message);
            // Obtains the status code from the response header.
            CFIndex statusCode = CFHTTPMessageGetResponseStatusCode(message);
    
            if (!self.responseIsHandle) {
              NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:_curRequest.URL statusCode:statusCode
                                                       HTTPVersion:(__bridge NSString *) httpVersion headerFields:headDict];
              [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
              self.responseIsHandle = YES;
            }
    
            // Verifies the certificate.
            SecTrustRef trust = (__bridge SecTrustRef) [aStream propertyForKey:(__bridge NSString *) kCFStreamPropertySSLPeerTrust];
            SecTrustResultType res = kSecTrustResultInvalid;
            NSMutableArray *policies = [NSMutableArray array];
            NSString *domain = [[_curRequest allHTTPHeaderFields] valueForKey:@"host"];
            if (domain) {
              [policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)];
            } else {
              [policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()];
            }
    
            // Binds the verification policy to the server certificate.
            SecTrustSetPolicies(trust, (__bridge CFArrayRef) policies);
            if (SecTrustEvaluate(trust, &res) != errSecSuccess) {
              [self closeStream:aStream];
              [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"can not evaluate the server trust" code:-1 userInfo:nil]];
              return;
            }
            if (res != kSecTrustResultProceed && res != kSecTrustResultUnspecified) {
              // If the certificate fails to be verified, close the input stream.
              [self closeStream:aStream];
              [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"fail to evaluate the server trust" code:-1 userInfo:nil]];
            } else {
              // The certificate is verified.
              if (statusCode >= 300 && statusCode < 400) {
                // Handles the redirection error code.
                [self closeStream:aStream];
                [self handleRedirect:message];
              } else {
                NSError *error = nil;
                NSData *data = [self readDataFromInputStream:inputstream headerDict:headDict stream:aStream error:&error];
                if (error) {
                  [self.client URLProtocol:self didFailWithError:error];
                } else {
                  [self.client URLProtocol:self didLoadData:data];
                }
              }
            }
          } else {
            NSError *error = nil;
            NSData *data = [self readDataFromInputStream:inputstream headerDict:headDict stream:aStream error:&error];
            if (error) {
              [self.client URLProtocol:self didFailWithError:error];
            } else {
              [self.client URLProtocol:self didLoadData:data];
            }
          }
          CFRelease((CFReadStreamRef)inputstream);
          CFRelease(message);
        }
      } else if (eventCode == NSStreamEventErrorOccurred) {
        [self closeStream:aStream];
        inflateEnd(&_gzipStream);
        // Notifies the client that an error occurred.
        [self.client URLProtocol:self didFailWithError:
             [[NSError alloc] initWithDomain:@"NSStreamEventErrorOccurred" code:-1 userInfo:nil]];
      } else if (eventCode == NSStreamEventEndEncountered) {
        CFReadStreamRef readStream = (__bridge_retained CFReadStreamRef) aStream;
        #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Wdeprecated-declarations"
        CFHTTPMessageRef message = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
        #pragma clang diagnostic pop
        if (CFHTTPMessageIsHeaderComplete(message)) {
          NSNumber *alreadyAdded = objc_getAssociatedObject(aStream, (__bridge const void *)(kAnchorAlreadyAdded));
          NSDictionary *headDict = (__bridge NSDictionary *) (CFHTTPMessageCopyAllHeaderFields(message));
    
          if (!alreadyAdded || ![alreadyAdded boolValue]) {
            objc_setAssociatedObject(aStream, (__bridge const void *)(kAnchorAlreadyAdded), [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_COPY);
            // Notifies the client that the response is received. This notification is sent only once.
    
            if (!self.responseIsHandle) {
              CFStringRef httpVersion = CFHTTPMessageCopyVersion(message);
              // Obtains the status code from the response header.
              CFIndex statusCode = CFHTTPMessageGetResponseStatusCode(message);
              NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:_curRequest.URL statusCode:statusCode
                                                       HTTPVersion:(__bridge NSString *) httpVersion headerFields:headDict];
              [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
              self.responseIsHandle = YES;
            }
          }
        }
    
        [self closeStream:_inputStream];
        inflateEnd(&_gzipStream);
        [self.client URLProtocolDidFinishLoading:self];
      }
    }
    
    - (NSData *)readDataFromInputStream:(NSInputStream *)inputStream headerDict:(NSDictionary *)headDict stream:(NSStream *)aStream error:(NSError **)error {
      // In case the response header is incomplete.
      UInt8 buffer[16 * 1024];
    
      NSInteger length = [inputStream read:buffer maxLength:sizeof(buffer)];
      if (length < 0) {
        *error = [[NSError alloc] initWithDomain:@"inputstream length is invalid"
                      code:-2
                      userInfo:nil];
        [aStream removeFromRunLoop:_curRunLoop forMode:NSRunLoopCommonModes];
        [aStream setDelegate:nil];
        [aStream close];
        return nil;
      }
    
      NSData *data = [[NSData alloc] initWithBytes:buffer length:length];
      if (headDict[@"Content-Encoding"] && [headDict[@"Content-Encoding"] containsString:@"gzip"]) {
        data = [self gzipUncompress:data];
        if (!data) {
          *error = [[NSError alloc] initWithDomain:@"can't read any data"
                          code:-3
                          userInfo:nil];
          return nil;
        }
      }
    
      return data;
    }
    
    - (void)closeStream:(NSStream*)stream {
      [stream removeFromRunLoop:_curRunLoop forMode:NSRunLoopCommonModes];
      [stream setDelegate:nil];
      [stream close];
    }
    
    - (void)handleRedirect:(CFHTTPMessageRef)messageRef {
      // Response header.
      CFDictionaryRef headerFieldsRef = CFHTTPMessageCopyAllHeaderFields(messageRef);
      NSDictionary *headDict = (__bridge_transfer NSDictionary *)headerFieldsRef;
      [self redirect:headDict];
    }
    
    - (void)redirect:(NSDictionary *)headDict {
      // If cookies are required for redirection, handle them.
      NSString *location = headDict[@"Location"];
      if (!location)
        location = headDict[@"location"];
      NSURL *url = [[NSURL alloc] initWithString:location];
      _curRequest.URL = url;
      if ([[_curRequest.HTTPMethod lowercaseString] isEqualToString:@"post"]) {
        // According to the RFC documentation, when a POST request is redirected, it must be converted to a GET request.
        _curRequest.HTTPMethod = @"GET";
        _curRequest.HTTPBody = nil;
      }
      [self startRequest];
    }
    
    - (NSData *)gzipUncompress:(NSData *)gzippedData {
      if ([gzippedData length] == 0) {
        return gzippedData;
      }
    
      unsigned full_length = (unsigned) [gzippedData length];
      unsigned half_length = (unsigned) [gzippedData length] / 2;
    
      NSMutableData *decompressed = [NSMutableData dataWithLength:full_length + half_length];
      BOOL done = NO;
      int status;
      _gzipStream.next_in = (Bytef *)[gzippedData bytes];
      _gzipStream.avail_in = (uInt)[gzippedData length];
      _gzipStream.total_out = 0;
    
      while (_gzipStream.avail_in != 0 && !done) {
        if (_gzipStream.total_out >= [decompressed length]) {
          [decompressed increaseLengthBy:half_length];
        }
    
        _gzipStream.next_out = (Bytef *)[decompressed mutableBytes] + _gzipStream.total_out;
        _gzipStream.avail_out = (uInt)([decompressed length] - _gzipStream.total_out);
    
        status = inflate(&_gzipStream, Z_SYNC_FLUSH);
    
        if (status == Z_STREAM_END) {
          done = YES;
        } else if (status == Z_BUF_ERROR) {
          // If Z_BUF_ERROR is caused by an insufficient output buffer, the input buffer is not fully processed and the output buffer is full. In this case, the loop must continue to expand the buffer.
          // The opposite condition indicates that the error is not caused by an insufficient output buffer. In this case, the loop must be terminated, which indicates an error.
          if (_gzipStream.avail_in == 0 || _gzipStream.avail_out != 0) {
            return nil;
          }
        } else if (status != Z_OK) {
                return nil;
            }
        }
    
        [decompressed setLength:_gzipStream.total_out];
        return [NSData dataWithData:decompressed];
    }
    
    @end
    
  2. Implemente um BeforeSend personalizado.

    - (void)setupHttpDNS:(NSString *)accountId {
      // Configures HTTPDNS.
      HttpDnsService *httpdns = [[HttpDnsService alloc] initWithAccountID:accountId];
      [httpdns setHTTPSRequestEnabled:YES];
      [httpdns setPersistentCacheIPEnabled:YES];
      [httpdns setReuseExpiredIPEnabled:YES];
      [httpdns setIPv6Enabled:YES];
    
      NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    
      NSMutableArray *protocolsArray = [NSMutableArray arrayWithArray:configuration.protocolClasses];
      // Sets the custom NSURLProtocol defined above.
      [protocolsArray insertObject:[HttpDnsNSURLProtocolImpl class] atIndex:0];
      [configuration setProtocolClasses:protocolsArray];
      NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:nil delegateQueue:nil];
    
      [SLSURLSession setURLSession:session];
      [SLSURLSession setBeforeSend:^NSMutableURLRequest * _Nonnull(NSMutableURLRequest * _Nonnull request) {
        NSURL *url = request.URL;
        // You can add a condition here to apply this only to required URLs.
        
        HttpdnsResult *result = [httpdns resolveHostSync:url.host byIpType:HttpdnsQueryIPTypeAuto];
        if (!result) {
          return request;
        }
    
        NSString *ipAddress = nil;
        if (result.hasIpv4Address) {
          ipAddress = result.firstIpv4Address;
        } else if(result.hasIpv6Address) {
          ipAddress = result.firstIpv6Address;
        } else {
          return request;
        }
    
        NSString *requestUrl = url.absoluteString;
        requestUrl = [requestUrl stringByReplacingOccurrencesOfString: url.host withString:ipAddress];
    
        [request setURL:[NSURL URLWithString:requestUrl]];
        [request setValue:url.host forHTTPHeaderField:@"host"];
    
        return request;
      }];
    }
  3. Conclua a inicialização do SLS SDK.

    @implementation AliyunSLS
    
    - (void)initSLS {
      // Sets custom HTTPDNS for the SLS SDK.
      // !!!Note!!!
      // This setting takes effect for all SLS SDK instances.
      [self setupHttpDNS:accountId];
      [self initProducer];
    }
    
    - (void)initProducer {
        // Initialize LogProducerConfig and LogProducerClient as you normally would.
        // ...
    }
    
    @end