
Agora, quase 100% dos aplicativos usam redes, para que todos se deparem com a organização e o uso da camada de rede. Existem duas abordagens principais para solucionar esse problema: o uso de bibliotecas de terceiros ou a sua própria implementação da camada de rede. Neste artigo, consideraremos a segunda opção e tentaremos implementar uma camada de rede usando todos os recursos mais recentes da linguagem, usando protocolos e enumerações. Isso salvará o projeto de dependências desnecessárias na forma de bibliotecas adicionais. Aqueles que já viram Moya reconhecerão imediatamente muitos detalhes semelhantes na implementação e uso, do jeito que são, só que desta vez faremos sozinhos sem tocar em Moya e Alamofire.
Neste guia, veremos como implementar uma camada de rede no Swift puro, sem usar bibliotecas de terceiros. Depois de revisar este artigo, seu código se tornará
- protocolo orientado
- fácil de usar
- fácil de usar
- tipo seguro
- para endpoints enums serão usados
Abaixo está um exemplo de como o uso da nossa camada de rede cuidará de sua implementação:

Simplesmente escrevendo
router.request (. E usando todo o poder das enumerações, veremos todas as opções de consulta possíveis e seus parâmetros.
Primeiro, um pouco sobre a estrutura do projetoSempre que você cria algo novo, e para poder entender tudo facilmente no futuro, é muito importante organizar e estruturar tudo corretamente. Acredito que uma estrutura de pastas organizada adequadamente é um detalhe importante ao construir a arquitetura do aplicativo. Para que possamos organizar tudo corretamente em pastas, vamos criá-las antecipadamente. Isso se parecerá com a estrutura geral de pastas no projeto:
Protocolo do tipo de ponto finalPrimeiro de tudo, precisamos definir nosso protocolo
EndPointType . Este protocolo conterá todas as informações necessárias para configurar a solicitação. O que é uma solicitação (ponto de extremidade)? Em essência, é um URLRequest com todos os componentes relacionados, como cabeçalhos, parâmetros de solicitação, corpo da solicitação. O protocolo
EndPointType é a parte mais importante da nossa implementação da camada de rede. Vamos criar um arquivo e
denomine -o
EndPointType . Coloque esse arquivo na pasta Serviço (não na pasta EndPoint, por quê - ficará claro mais tarde)
Protocolos HTTPNosso
EndPointType contém vários protocolos que precisamos para criar uma solicitação. Vamos ver o que são esses protocolos.
HTTPMethodCrie um arquivo, nomeie-o
HTTPMethod e coloque-o na pasta Serviço. Esta listagem será usada para definir o método HTTP de nossa solicitação.
HTTPTaskCrie um arquivo,
chame -o de
HTTPTask e coloque-o na pasta Serviço. HTTPTask é responsável por configurar os parâmetros de uma solicitação específica. Você pode adicionar quantas opções de consulta diferentes forem necessárias, mas eu, por sua vez, vou fazer consultas regulares, consultas com parâmetros, consultas com parâmetros e cabeçalhos, portanto, somente esses três tipos de consulta serão feitos.

Na próxima seção, discutiremos os
parâmetros e como trabalharemos com eles
HTTPHeadersOs HTTPHeaders são apenas tipos de letra para um dicionário. Você pode criá-lo na parte superior do seu arquivo
HTTPTask .
public typealias HTTPHeaders = [String:String]
Parâmetros e codificaçãoCrie um arquivo, nomeie-o como
ParameterEncoding e coloque-o na pasta Encoding. Crie typealias para
Parameters , será novamente um dicionário regular. Fazemos isso para tornar o código mais compreensível e legível.
public typealias Parameters = [String:Any]
Em seguida, defina um protocolo
ParameterEncoder com uma única função de codificação. O método de codificação possui dois parâmetros:
inout URLRequest e
Parameters .
INOUT é uma palavra-chave Swift que define um parâmetro de função como uma referência. Normalmente, os parâmetros são passados para a função como valores. Ao escrever
inout antes de um parâmetro de função em uma chamada, você define esse parâmetro como um tipo de referência. Para saber mais sobre argumentos inout, você pode seguir este link. Em resumo,
inout permite alterar o valor da variável em si, que foi passada para a função, e não apenas obter seu valor no parâmetro e trabalhar com ele dentro da função. O protocolo
ParameterEncoder será implementado no
JSONParameterEncoder e no
URLPameterEncoder .
public protocol ParameterEncoder { static func encode(urlRequest: inout URLRequest, with parameters: Parameters) throws }
ParameterEncoder contém uma única função cuja tarefa é codificar parâmetros. Esse método pode gerar um erro que precisa ser tratado, então usamos o throw.
Também pode ser útil produzir erros não padrão, mas personalizados. É sempre muito difícil descriptografar o que o Xcode oferece a você. Quando você tem todos os erros personalizados e descritos, sempre sabe exatamente o que aconteceu. Para fazer isso, vamos definir uma enumeração que herda de
Error .

Crie um arquivo, nomeie-o como
URLParameterEncoder e coloque-o na pasta
Encoding .

Este código pega uma lista de parâmetros, converte e formata-os para uso como parâmetros de URL. Como você sabe, alguns caracteres não são permitidos no URL. Os parâmetros também são separados pelo símbolo "&", portanto, devemos cuidar disso. Também devemos definir o valor padrão para os cabeçalhos se eles não estiverem definidos na solicitação.
Essa é a parte do código que deve ser coberta por testes de unidade. Construir uma solicitação de URL é a chave, caso contrário, podemos provocar muitos erros desnecessários. Se você usa a API aberta, obviamente não deseja usar todo o volume possível de solicitações para testes com falha. Se você quiser saber mais sobre testes de unidade, comece com este artigo.
JSONParameterEncoderCrie um arquivo,
denomine JSONParameterEncoder e coloque-o na pasta Encoding.

Tudo é o mesmo que no caso de
URLParameter , apenas aqui vamos converter os parâmetros para JSON e adicionar novamente os parâmetros que definem a codificação "application / json" ao cabeçalho.
NetworkrouterCrie um arquivo, nomeie-o
NetworkRouter e coloque-o na pasta Serviço. Vamos começar definindo tipealias para o fechamento.
public typealias NetworkRouterCompletion = (_ data: Data?,_ response: URLResponse?,_ error: Error?)->()
Em seguida, definimos o protocolo
NetworkRouter .
O NetworkRouter possui um
EndPoint que ele usa para solicitações e, assim que a solicitação é concluída, o resultado dessa solicitação é passado para o fechamento do
NetworkRouterCompletion . O protocolo também possui uma função de
cancelamento , que pode ser usada para interromper solicitações de carga e descarga de longo prazo. Também usamos o tipo
associado aqui, porque queremos que o nosso
roteador suporte qualquer tipo de
EndPointType . Sem usar o tipo associado, o roteador precisaria ter algum tipo específico que implemente
EndPointType . Se você quiser saber mais sobre o tipo associado, leia
este artigo .
RoteadorCrie um arquivo, chame-o de
roteador e coloque-o na pasta Serviço. Declaramos uma variável privada do tipo
URLSessionTask . Todo o trabalho estará nele. Tornamos privado, porque não queremos que ninguém de fora possa alterá-lo.
PedidoAqui criamos o
URLSession usando o
URLSession.shared , esta é a maneira mais fácil de criar. Mas lembre-se de que esse método não é o único. Você pode usar configurações de
URLSession mais complexas que podem alterar seu comportamento. Mais sobre isso
neste artigo .
A solicitação é criada chamando a função
buildRequest.A chamada de função é
agrupada em do-try-catch, porque as funções de codificação dentro de
buildRequest podem
gerar exceções.
Resposta ,
dados e
erro são passados para conclusão.
Pedido de compilaçãoCriamos nossa solicitação usando a função
buildRequest . Essa função é responsável por todo o trabalho vital em nossa camada de rede. Converte essencialmente
EndPointType em
URLRequest . E assim que o
EndPoint se transformar em uma solicitação, podemos passar para a
sessão . Muitas coisas estão acontecendo aqui, então vamos dar uma olhada nos métodos. Primeiro,
vamos examinar o método
buildRequest :
1. Inicializamos a variável de solicitação
URLRequest . Definimos nosso URL base e adicionamos o caminho da solicitação específica que será usada para ele.
2. Atribua
request.httpMethod o método http do nosso
EndPoint .
3. Criamos um bloco do-try-catch, porque nossos codificadores podem gerar um erro. Ao criar um grande bloco do-try-catch, eliminamos a necessidade de criar um bloco separado para cada tentativa.
4. No switch, verifique
route.task .
5. Dependendo do tipo de tarefa, chamamos o codificador correspondente.
Configurar parâmetrosCrie a função
configureParameters no roteador.

Essa função é responsável por converter nossos parâmetros de consulta. Como nossa API assume o uso de
bodyParameters na forma de JSON e
URLParameters convertidos para o formato de URL, simplesmente passamos os parâmetros apropriados para as funções de conversão correspondentes, descritas no começo do artigo. Se você usar uma API que inclua vários tipos de codificações, nesse caso, eu recomendaria adicionar
HTTPTask com uma enumeração adicional com o tipo de codificação. Esta listagem deve conter todos os tipos possíveis de codificações. Depois disso, em configureParameters, adicione mais um argumento com essa enumeração. Dependendo do seu valor, alterne usando switch e faça a codificação necessária.
Adicionar cabeçalhos adicionaisCrie a função
addAdditionalHeaders no roteador.

Basta adicionar todos os cabeçalhos necessários à solicitação.
CancelarA função de
cancelamento parecerá bastante simples:
Exemplo de usoAgora vamos tentar usar nossa camada de rede em um exemplo real. Vamos nos conectar ao
TheMovieDB para receber dados para o nosso aplicativo.
MovieEndPointCrie um arquivo
MovieEndPoint e coloque-o na pasta EndPoint. MovieEndPoint é o mesmo que
e TargetType em Moya. Aqui, implementamos nosso próprio EndPointType. Um artigo descrevendo como usar o Moya para um exemplo semelhante pode ser encontrado
neste link .
import Foundation enum NetworkEnvironment { case qa case production case staging } public enum MovieApi { case recommended(id:Int) case popular(page:Int) case newMovies(page:Int) case video(id:Int) } extension MovieApi: EndPointType { var environmentBaseURL : String { switch NetworkManager.environment { case .production: return "https:
MoviemodelPara analisar o modelo de dados
MovieModel e JSON no modelo, o protocolo Decodable é usado. Coloque esse arquivo na pasta
Modelo .
Nota : para um conhecimento mais detalhado dos protocolos Codable, Decodable e Encodable, você pode ler
meu outro artigo , que descreve em detalhes todos os recursos de trabalhar com eles.
import Foundation struct MovieApiResponse { let page: Int let numberOfResults: Int let numberOfPages: Int let movies: [Movie] } extension MovieApiResponse: Decodable { private enum MovieApiResponseCodingKeys: String, CodingKey { case page case numberOfResults = "total_results" case numberOfPages = "total_pages" case movies = "results" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: MovieApiResponseCodingKeys.self) page = try container.decode(Int.self, forKey: .page) numberOfResults = try container.decode(Int.self, forKey: .numberOfResults) numberOfPages = try container.decode(Int.self, forKey: .numberOfPages) movies = try container.decode([Movie].self, forKey: .movies) } } struct Movie { let id: Int let posterPath: String let backdrop: String let title: String let releaseDate: String let rating: Double let overview: String } extension Movie: Decodable { enum MovieCodingKeys: String, CodingKey { case id case posterPath = "poster_path" case backdrop = "backdrop_path" case title case releaseDate = "release_date" case rating = "vote_average" case overview } init(from decoder: Decoder) throws { let movieContainer = try decoder.container(keyedBy: MovieCodingKeys.self) id = try movieContainer.decode(Int.self, forKey: .id) posterPath = try movieContainer.decode(String.self, forKey: .posterPath) backdrop = try movieContainer.decode(String.self, forKey: .backdrop) title = try movieContainer.decode(String.self, forKey: .title) releaseDate = try movieContainer.decode(String.self, forKey: .releaseDate) rating = try movieContainer.decode(Double.self, forKey: .rating) overview = try movieContainer.decode(String.self, forKey: .overview) } }
Gerente de redeCrie um arquivo
NetworkManager na pasta Manager. No momento, o NetworkManager contém apenas duas propriedades estáticas: uma chave de API e uma enumeração que descreve o tipo de servidor ao qual se conectar.
O NetworkManager também contém um
roteador do tipo
MovieApi .
Resposta de redeCrie a enumeração
NetworkResponse no NetworkManager.

Usamos essa enumeração ao processar respostas a solicitações e exibiremos a mensagem correspondente.
ResultadoCrie uma enumeração de
resultados no NetworkManager.

Usamos
Result para determinar se a solicitação foi bem-sucedida ou não. Caso contrário, retornaremos uma mensagem de erro com o motivo.
Processamento de resposta de solicitaçãoCrie a função
handleNetworkResponse . Essa função usa um argumento, como um
HTTPResponse, e retorna Result.

Nesta função, dependendo do statusCode recebido do HTTPResponse, retornamos uma mensagem de erro ou um sinal de uma solicitação bem-sucedida. Normalmente, um código no intervalo de 200..299 significa sucesso.
Fazendo uma solicitação de redeEntão, fizemos tudo para começar a usar nossa camada de rede, vamos tentar fazer uma solicitação.
Solicitaremos uma lista de novos filmes. Crie uma função e
chame -a de
getNewMovies .

Vamos dar um passo a passo:
1. Definimos o método
getNewMovies com dois argumentos: o número da página de paginação e o manipulador de conclusão, que retorna uma matriz opcional de modelos de
filme ou um erro opcional.
2. Ligue para o
roteador . Passamos o número da página e
concluímos o processo no fechamento.
3.
URLSession retorna um erro se não houver rede ou não foi possível fazer uma solicitação por qualquer motivo. Observe que este não é um erro de API; esses erros ocorrem no cliente e geralmente ocorrem devido à baixa qualidade da conexão com a Internet.
4. Precisamos transmitir nossa
resposta ao
HTTPURLResponse , porque precisamos acessar a propriedade
statusCode .
5. Declare o
resultado e inicialize-o usando o método
handleNetworkResponse6.
Sucesso significa que a solicitação foi bem-sucedida e recebemos a resposta esperada. Depois, verificamos se os dados vieram com a resposta e, se não, simplesmente terminamos o método por meio de retorno.
7. Se a resposta vier com dados, é necessário analisar os dados recebidos no modelo. Depois disso, passamos a matriz de modelos resultante para conclusão.
8. Em caso de erro, basta passar o erro para
conclusão .
É isso, é assim que nossa própria camada de rede funciona no Swift puro, sem usar nenhuma dependência na forma de pods e bibliotecas de terceiros. Para fazer uma solicitação de API de teste para obter uma lista de filmes, crie um MainViewController com a propriedade
NetworkManager e chame o método
getNewMovies através dele.
class MainViewController: UIViewController { var networkManager: NetworkManager! init(networkManager: NetworkManager) { super.init(nibName: nil, bundle: nil) self.networkManager = networkManager } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .green networkManager.getNewMovies(page: 1) { movies, error in if let error = error { print(error) } if let movies = movies { print(movies) } } } }
Bônus pequenoVocê teve situações no Xcode quando não entendeu que tipo de espaço reservado é usado em um local específico? Por exemplo, veja o código que acabamos de escrever para o
roteador .

Nós mesmos determinamos o
NetworkRouterCompletion , mas mesmo neste caso, é fácil esquecer que tipo e como usá-lo. Mas nosso amado Xcode cuidou de tudo, e basta clicar duas vezes no espaço reservado e o Xcode substituirá o tipo desejado.
ConclusãoAgora, temos uma implementação de uma camada de rede orientada a protocolo, que é muito fácil de usar e que você sempre pode personalizar de acordo com suas necessidades. Compreendemos sua funcionalidade e como todos os mecanismos funcionam.
Você pode encontrar o código fonte
neste repositório .