Communitygithub.com

SwiftMesh

Uma referência abrangente de IA para usar SwiftMesh — um wrapper Alamofire + Codable com async/await, Combine, configuração fluente, upload/download de arquivos, análise de caminhos JSON, wrappers Codable resilientes e logging integrado.

O que é SwiftMesh?

SwiftMesh é uma skill de IA projetada para desenvolvedores iOS que desejam simplificar a comunicação com APIs REST usando Swift. Ela atua como um wrapper sobre Alamofire e Codable, oferecendo uma API fluente baseada em construtor para configurar e executar requisições de rede com async/await ou Combine. A skill suporta uma ampla gama de operações, incluindo requisições GET e POST, upload de arquivos (simples e multipart), download de arquivos (com suporte a retomada), e análise de dados JSON usando caminhos de chave (key path) para extrair valores aninhados sem decodificar a estrutura completa. Além disso, o SwiftMesh fornece wrappers Codable resilientes (como @Default, @IgnoreError, @ConvertToString) para lidar com respostas de API inconsistentes sem causar falhas de decodificação, e inclui um sistema de logging integrado e política de repetição automática (RetryPolicy). Esta skill é útil para qualquer agente de IA que precise gerar ou entender código Swift para networking robusto e eficiente, especialmente em projetos que utilizam SwiftUI ou UIKit com Combine. O fluxo de uso típico segue o padrão: Configure (com a classe Mesh) → Execute (requisição, upload ou download) → Trate (com o bloco Handle).

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add zjinhu/SwiftMesh

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

SwiftMesh AI Skill

Uma referência abrangente de IA para usar SwiftMesh — um wrapper Alamofire + Codable com async/await, Combine, configuração fluente, upload/download de arquivos, análise de caminhos JSON, wrappers Codable resilientes e logging integrado.


Cartão de Referência Rápida

RecursoMétodoDescrição
Requisição GET.request(of: Model.self)Decodifica a resposta para um modelo Codable
Caminho de Chave.request(of: Model.self, modelKeyPath: "data.user")Extrai JSON aninhado
Upload de Arquivo.upload(of: Result.self)Faz upload de arquivo/Dados/stream/multipart
Download de Arquivo.download()Baixa ou retoma download
Dados Brutos.requestData()Obtém dados brutos da resposta
String Bruta.requestString()Obtém a resposta como String
Política de Repetição.setInterceptor(RetryPolicy())Repetição automática com backoff
LoggingMesh.enableLog()Ativa o logging de rede

Arquitetura Principal

O SwiftMesh usa um padrão de construtor na classe Mesh. Cada método de configuração retorna Self, permitindo encadeamento fluente. O fluxo é:

Configure (Mesh + Config) → Execute (Requisição/Upload/Download) → Trate (Handle)

Estrutura de Arquivos

ArquivoPropósito
Mesh.swiftClasse construtora principal com todas as propriedades
Config.swiftSetters encadeáveis fluentes + configuração global
Request.swiftExecução de requisição com async/await
Handle.swiftConstrução de URL, tratamento de erros, processamento de resposta, RetryPolicy
Upload.swiftUpload de arquivo (arquivo, dados, stream, multipart)
Download.swiftDownload de arquivo (padrão, retomável)
KeyPath.swiftDecodificador de caminho JSON para extração aninhada
Codable+.swiftWrappers de propriedade resilientes (@Default, @IgnoreError, @ConvertTo*)
Log.swiftLogger de rede (cURL, status, tempo, JSON)

Padrões de Uso

1. Requisição GET Básica

let result = try await Mesh()
    .setRequestMethod(.get)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/weather/city/101030100")
    .request(of: Weather.self)

2. GET com Extração de Caminho JSON

Extraia apenas uma parte aninhada da resposta JSON sem analisar a estrutura completa:

// JSON: { "code": 200, "data": { "yesterday": { "temp": 25, "notice": "Sunny" } } }
let yesterday = try await Mesh()
    .setRequestMethod(.get)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/weather")
    .request(of: Forecast.self, modelKeyPath: "data.yesterday")

3. Requisição POST com Parâmetros

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/login")
    .setParameters(["username": "admin", "password": "123456"])
    .request(of: LoginResult.self)

4. POST com Codificação JSON

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/api/data")
    .setRequestEncoding(JSONEncoding.default)
    .setParameters(["key": "value"])
    .request(of: Response.self)

5. Requisição com Cabeçalhos Personalizados

let result = try await Mesh()
    .setUrlHost("https://api.example.com")
    .setUrlPath("/secure/data")
    .setHeads(["Authorization": "Bearer token123"])
    .request(of: SecureData.self)

6. Requisição com Política de Repetição

let result = try await Mesh()
    .setUrlHost("https://api.example.com")
    .setUrlPath("/unstable-api")
    .setInterceptor(RetryPolicy(maxRetryCount: 3))
    .request(of: Data.self)

7. Requisição Baseada em URLRequest

Quando você já tem um URLRequestConvertible:

let urlRequest = try URLRequest(url: URL(string: "https://api.example.com/data")!, method: .get)
let result = try await Mesh()
    .urlRequest(urlRequest, type: Response.self)

8. Resposta de Dados Brutos

let data = try await Mesh()
    .setRequestMethod(.get)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/raw")
    .requestData()

9. Resposta de String Bruta

let string = try await Mesh()
    .setRequestMethod(.get)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/text")
    .requestString()

10. Download de Arquivo

let fileURL = try await Mesh()
    .setRequestMethod(.get)
    .setUrlHost("https://example.com")
    .setUrlPath("/files/document.pdf")
    .setDestination { _, _ in
        let dest = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            .appendingPathComponent("document.pdf")
        return (dest, [.removePreviousFile, .createIntermediateDirectories])
    }
    .download()

11. Download Retomável

let fileURL = try await Mesh()
    .setUrlHost("https://example.com")
    .setUrlPath("/files/large-file.zip")
    .setDownloadType(.resume)
    .setResumeData(savedResumeData)  // de download interrompido anteriormente
    .download()

12. Upload de Arquivo Único (URL)

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/upload")
    .setUploadType(.file)
    .setFileURL(fileURL)
    .upload(of: UploadResult.self)

13. Upload de Arquivo Único (Dados)

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/upload")
    .setUploadType(.data)
    .setFileData(imageData)
    .upload(of: UploadResult.self)

14. Upload de Formulário Multipart

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/upload/multi")
    .setUploadType(.multipart)
    .setAddformData(name: "file",
                    fileName: "photo.jpg",
                    fileData: imageData,
                    mimeType: "image/jpeg")
    .setAddformData(name: "description",
                    fileData: "My photo".data(using: .utf8))
    .upload(of: UploadResult.self)

15. Multipart com UploadDatas Pré-construídos

let uploads = [
    MultipleUpload.formData(name: "file1", fileName: "a.jpg", fileData: data1, mimeType: "image/jpeg"),
    MultipleUpload.formData(name: "file2", fileName: "b.pdf", fileURL: fileURL)
]

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/upload/batch")
    .setUploadType(.multipart)
    .setUploadDatas(uploads)
    .setParameters(["userId": "123"])  // campos de formulário adicionais
    .upload(of: BatchResult.self)

16. Timeout Personalizado

let result = try await Mesh()
    .setUrlHost("https://api.example.com")
    .setUrlPath("/slow-api")
    .setTimeout(60)  // 60 segundos
    .request(of: Response.self)

Configuração Global

Defina isso uma vez na inicialização do aplicativo (por exemplo, em AppDelegate):

// Ativar logging de rede
Mesh.enableLog(.log)  // ou .print

// Definir cabeçalhos padrão globais
Mesh.setHeaders(["Authorization": "Bearer token", "App-Version": "1.0"])

// Definir parâmetros padrão globais
Mesh.setParameters(["platform": "ios", "sdk_version": "2.0"])

// Definir host de URL global
Mesh.setUrlHost("https://api.example.com")

Então, a configuração por requisição precisa apenas do caminho:

// Usa cabeçalhos globais, parâmetros e urlHost
let result = try await Mesh()
    .setUrlPath("/weather")
    .request(of: Weather.self)

Wrappers de Propriedade Codable Resilientes

Lida com respostas de API inconsistentes de forma graciosa, sem falhas de decodificação.

Wrappers @Default

Fornece valores de fallback quando campos estão ausentes ou inválidos:

struct Response: Codable {
    @Default.True var isEnabled: Bool              // Ausente → true
    @Default.False var isDeleted: Bool             // Ausente → false
    @Default.EmptyString var name: String          // Ausente → ""
    @Default.EmptyInt var count: Int               // Ausente → 0
    @Default.EmptyArray var tags: [String]         // Ausente → []
    @Default.EmptyDictionary var meta: [String: Int] // Ausente → [:]
    @Default.Now var createdAt: Date               // Ausente → Date()
}

@IgnoreError

Retorna nil em vez de lançar uma exceção quando um campo falha ao decodificar:

struct Response: Codable {
    @IgnoreError var description: String?   // Tipo inválido → nil (sem crash)
    @IgnoreError var nested: NestedModel?   // Ausente/ malformado → nil
}

@ConvertToString

Aceita String, Int ou Double do JSON → converte para String?:

struct Response: Codable {
    @ConvertToString var version: String?  // "1.0", 1, ou 1.0 → "1.0", "1", "1.0"
}

@ConvertToInt

Aceita Int, String ou Double do JSON → converte para Int?:

struct Response: Codable {
    @ConvertToInt var count: Int?  // 42, "42", ou 42.9 → 42, 42, 42
}

@ConvertToDouble

Aceita Double, Int, Float ou String do JSON → converte para Double?:

struct Response: Codable {
    @ConvertToDouble var price: Double?  // 9.99, 10, "9.99" → 9.99, 10.0, 9.99
}

@ConvertToFloat

Aceita Float, Int, Double ou String do JSON → converte para Float?:

struct Response: Codable {
    @ConvertToFloat var rating: Float?  // 4.5, 5, "4.5" → 4.5, 5.0, 4.5
}

Decodificador de Caminho JSON

Extraia valores JSON aninhados sem analisar a resposta inteira:

// JSON: { "code": 200, "data": { "list": [{ "id": 1 }, { "id": 2 }] } }

// Extrai um único objeto aninhado
let firstItem = try await Mesh()
    .setRequestMethod(.get)
    .setUrlPath("/items")
    .request(of: Item.self, modelKeyPath: "data.list.0")

// O decodificador KeyPath também funciona diretamente:
let decoder = JSONDecoder.default
let item = try decoder.decode(Item.self, from: jsonData, keyPath: "data.list.0")
let items = try decoder.decodeArray([Item].self, from: jsonData, keyPath: "data.list")

O decodificador padrão configura automaticamente:

  • keyDecodingStrategy = .convertFromSnakeCase (snake_case → camelCase)
  • dateDecodingStrategy = .iso8601

Integração Combine + SwiftUI

Padrão ObservableObject

class RequestModel: ObservableObject {
    @MainActor @Published var weather: Weather?
    @MainActor @Published var errorMessage: String?
    
    func fetchWeather() {
        Task {
            do {
                let result = try await Mesh()
                    .setRequestMethod(.get)
                    .setUrlHost("https://api.example.com")
                    .setUrlPath("/weather")
                    .request(of: Weather.self)
                
                await MainActor.run { self.weather = result }
            } catch {
                await MainActor.run { self.errorMessage = error.localizedDescription }
            }
        }
    }
}

Uso em SwiftUI

struct WeatherView: View {
    @StateObject private var model = RequestModel()
    
    var body: some View {
        VStack {
            if let weather = model.weather {
                Text("\(weather.temperature)°C")
            }
            if let error = model.errorMessage {
                Text(error).foregroundColor(.red)
            }
        }
        .onAppear { model.fetchWeather() }
    }
}

Uso em UIKit + Combine

class ViewController: UIViewController {
    private var model = RequestModel()
    private var cancellables: Set<AnyCancellable> = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        model.$weather
            .receive(on: RunLoop.main)
            .sink { weather in
                print("Clima: \(String(describing: weather))")
            }
            .store(in: &cancellables)
        
        model.fetchWeather()
    }
}

Tratamento de Erros

O SwiftMesh normaliza erros de rede comuns em NSError amigável ao usuário:

Código de ErroDescrição
NSURLErrorNotConnectedToInternetSem conexão com a internet
NSURLErrorTimedOutRequisição excedeu o tempo limite
NSURLErrorCannotFindHostHost não encontrado
NSURLErrorCannotConnectToHostNão é possível conectar ao host
NSURLErrorNetworkConnectionLostConexão perdida durante a requisição

Todos os erros normalizados retornam a mensagem: "Não foi possível conectar ao servidor"

Capture erros no seu código assíncrono:

do {
    let result = try await Mesh()
        .setUrlPath("/api")
        .request(of: Response.self)
} catch let error as NSError {
    print("Erro \(error.code): \(error.localizedDescription)")
}

RetryPolicy

Repetição interna com backoff linear (1s, 2s, 3s...):

// Padrão: 3 repetições
let policy = RetryPolicy()

// Personalizado: 5 repetições
let policy = RetryPolicy(maxRetryCount: 5)

// Usar com requisição
let result = try await Mesh()
    .setUrlPath("/unstable-api")
    .setInterceptor(policy)
    .request(of: Response.self)

Logging

Ativar na inicialização do aplicativo:

// Usando o Logger unificado da Apple (os.log)
Mesh.enableLog(.log)

// Usando print() do Swift
Mesh.enableLog(.print)

A saída inclui:

  • Comando cURL (para fácil reprodução)
  • Código de status HTTP
  • Tempo decorrido
  • Resposta JSON formatada

Referência de Métodos de Configuração

Todos os métodos são encadeáveis (retornam Self):

Configuração Básica

MétodoTipoDescrição
.setTimeout(_:)TimeIntervalTempo limite da requisição em segundos (padrão: 15)
.setInterceptor(_:)RequestInterceptor?Política de repetição / manipulador de credenciais
.setRequestMethod(_:)HTTPMethodGET, POST, PUT, DELETE, etc.
.setHeads(_:)[String: String]Cabeçalhos por requisição
.setRequestEncoding(_:)ParameterEncodingURLEncoding, JSONEncoding, etc.
.setUrlHost(_:)String?Host de URL por requisição
.setUrlPath(_:)String?Caminho da URL
.setParameters(_:)[String: Any]?Parâmetros da requisição

Configuração de Download

MétodoTipoDescrição
.setDownloadType(_:)DownloadType.download ou .resume
.setResumeData(_:)Data?Dados de retomada para download retomável
.setDestination(_:)DownloadRequest.Destination?Caminho de destino do arquivo baixado

Configuração de Upload

MétodoTipoDescrição
.setUploadType(_:)UploadType.file, .data, .stream ou .multipart
.setFileURL(_:)URL?URL do arquivo para upload de arquivo
.setFileData(_:)Data?Dados para upload de dados
.setStream(_:)InputStream?Stream para upload de stream
.setAddformData(_:fileData:mimeType:name:)-Adiciona um único campo/formData multipart
.setUploadDatas(_:)[MultipleUpload]Array de múltiplos dados de upload multipart

Configuração de Análise de Resposta

MétodoTipoDescrição
.setOutDataType(_:)OutDataType.data, .string, .jsonObject, .model
.setModelType(_:)Codable.Type?Tipo do modelo para decodificação na resposta
.setModelKeyPath(_:)String?Caminho de chave JSON para extrair dados aninhados

Organização do Contexto da Skill

Esta documentação está estruturada para consulta rápida em tarefas de agentes:

  1. Cartão de Referência Rápida — Principais recursos e métodos para uso rápido
  2. Arquitetura Principal — Entenda o padrão de construtor e a estrutura de arquivos
  3. Padrões de Uso — Exemplos de código copiáveis para casos de uso comuns
  4. Configuração Global — Configuração única para toda a sessão
  5. Codable Resiliente — Manipulação graciosa de respostas de API irregulares
  6. Caminho JSON — Extração de dados aninhados sem analisar tudo
  7. Combine + SwiftUI — Integração reativa com IU
  8. Tratamento de Erros — Normalização de erros de rede
  9. RetryPolicy — Repetição automática com backoff
  10. Logging — Depuração e monitoramento
  11. Referência de Métodos — Tabela de métodos de configuração

Propósito da Skill

Esta skill de IA fornece documentação abrangente e exemplos de código para o SwiftMesh, uma biblioteca Swift para requisições de rede. O objetivo é permitir que agentes de IA gerem código Swift preciso, idiomático e pronto para uso utilizando o SwiftMesh. A skill cobre todos os principais recursos, desde requisições básicas GET até uploads multipart complexos, download retomável e decodificação JSON resiliente.

Habilidades Relacionadas