Habilidad de IA SwiftMesh
Una referencia integral de IA para usar SwiftMesh: un envoltorio de Alamofire + Codable con async/await, Combine, configuración fluida, carga/descarga de archivos, análisis de rutas de claves JSON, envoltorios Codable resilientes y registro integrado.
Tarjeta de Referencia Rápida
| Característica | Método | Descripción |
|---|---|---|
| Solicitud GET | .request(of: Model.self) | Decodificar respuesta a modelo Codable |
| Ruta de clave | .request(of: Model.self, modelKeyPath: "data.user") | Extraer JSON anidado |
| Subida de archivo | .upload(of: Result.self) | Subir archivo/Datos/flujo/multipart |
| Descarga de archivo | .download() | Descargar o reanudar descarga |
| Datos sin procesar | .requestData() | Obtener datos de respuesta sin procesar |
| Cadena sin procesar | .requestString() | Obtener respuesta como cadena |
| Política de reintento | .setInterceptor(RetryPolicy()) | Reintento automático con retroceso |
| Registro | Mesh.enableLog() | Habilitar registro de red |
Arquitectura Principal
SwiftMesh usa un patrón de constructor en la clase Mesh. Cada método de configuración devuelve Self, permitiendo encadenamiento fluido. El flujo es:
Configurar (Mesh + Config) → Ejecutar (Solicitud/Subida/Descarga) → Manejar (Handle)
Estructura de Archivos
| Archivo | Propósito |
|---|---|
Mesh.swift | Clase constructora principal con todas las propiedades |
Config.swift | Setters encadenables fluidos + configuración global |
Request.swift | Ejecución de solicitud async/await |
Handle.swift | Construcción de URL, manejo de errores, procesamiento de respuestas, RetryPolicy |
Upload.swift | Subida de archivos (archivo, datos, flujo, multipart) |
Download.swift | Descarga de archivos (estándar, reanudable) |
KeyPath.swift | Decodificador de rutas de claves JSON para extracción anidada |
Codable+.swift | Envoltorios de propiedades resilientes (@Default, @IgnoreError, @ConvertTo*) |
Log.swift | Registrador de red (cURL, estado, tiempo, JSON) |
Patrones de Uso
1. Solicitud 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 con Extracción de Ruta de Clave JSON
Extraer solo una parte anidada de la respuesta JSON sin analizar toda la estructura:
// 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. Solicitud POST con 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 con Codificación 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. Solicitud con Encabezados Personalizados
let result = try await Mesh()
.setUrlHost("https://api.example.com")
.setUrlPath("/secure/data")
.setHeads(["Authorization": "Bearer token123"])
.request(of: SecureData.self)
6. Solicitud con Política de Reintento
let result = try await Mesh()
.setUrlHost("https://api.example.com")
.setUrlPath("/unstable-api")
.setInterceptor(RetryPolicy(maxRetryCount: 3))
.request(of: Data.self)
7. Solicitud Basada en URLRequest
Cuando ya tienes un 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. Respuesta de Datos Sin Procesar
let data = try await Mesh()
.setRequestMethod(.get)
.setUrlHost("https://api.example.com")
.setUrlPath("/raw")
.requestData()
9. Respuesta de Cadena Sin Procesar
let string = try await Mesh()
.setRequestMethod(.get)
.setUrlHost("https://api.example.com")
.setUrlPath("/text")
.requestString()
10. Descarga de Archivo
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. Descarga Reanudable
let fileURL = try await Mesh()
.setUrlHost("https://example.com")
.setUrlPath("/files/large-file.zip")
.setDownloadType(.resume)
.setResumeData(savedResumeData) // de descarga interrumpida anterior
.download()
12. Subida de Archivo Ú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. Subida de Archivo Único (Datos)
let result = try await Mesh()
.setRequestMethod(.post)
.setUrlHost("https://api.example.com")
.setUrlPath("/upload")
.setUploadType(.data)
.setFileData(imageData)
.upload(of: UploadResult.self)
14. Subida de Formulario 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 con UploadDatas Preconstruidos
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 formulario adicionales
.upload(of: BatchResult.self)
16. Tiempo de Espera Personalizado
let result = try await Mesh()
.setUrlHost("https://api.example.com")
.setUrlPath("/slow-api")
.setTimeout(60) // 60 segundos
.request(of: Response.self)
Configuración Global
Establece estos una vez al iniciar la aplicación (por ejemplo, en AppDelegate):
// Habilitar registro de red
Mesh.enableLog(.log) // o .print
// Establecer encabezados globales predeterminados
Mesh.setHeaders(["Authorization": "Bearer token", "App-Version": "1.0"])
// Establecer parámetros globales predeterminados
Mesh.setParameters(["platform": "ios", "sdk_version": "2.0"])
// Establecer host URL global
Mesh.setUrlHost("https://api.example.com")
Luego, la configuración por solicitud solo necesita la ruta:
// Usa encabezados, parámetros y urlHost globales
let result = try await Mesh()
.setUrlPath("/weather")
.request(of: Weather.self)
Envoltorios de Propiedades Codable Resilientes
Maneja respuestas de API inconsistentes sin fallos de decodificación.
Envoltorios @Default
Proporciona valores de respaldo cuando faltan campos o son inválidos:
struct Response: Codable {
@Default.True var isEnabled: Bool // Falta → true
@Default.False var isDeleted: Bool // Falta → false
@Default.EmptyString var name: String // Falta → ""
@Default.EmptyInt var count: Int // Falta → 0
@Default.EmptyArray var tags: [String] // Falta → []
@Default.EmptyDictionary var meta: [String: Int] // Falta → [:]
@Default.Now var createdAt: Date // Falta → Date()
}
@IgnoreError
Devuelve nil en lugar de lanzar una excepción cuando un campo no se puede decodificar:
struct Response: Codable {
@IgnoreError var description: String? // Tipo inválido → nil (sin colapso)
@IgnoreError var nested: NestedModel? // Faltante/malformado → nil
}
@ConvertToString
Acepta String, Int o Double de JSON → convierte a String?:
struct Response: Codable {
@ConvertToString var version: String? // "1.0", 1 o 1.0 → "1.0", "1", "1.0"
}
@ConvertToInt
Acepta Int, String o Double de JSON → convierte a Int?:
struct Response: Codable {
@ConvertToInt var count: Int? // 42, "42" o 42.9 → 42, 42, 42
}
@ConvertToDouble
Acepta Double, Int, Float o String de JSON → convierte a Double?:
struct Response: Codable {
@ConvertToDouble var price: Double? // 9.99, 10, "9.99" → 9.99, 10.0, 9.99
}
@ConvertToFloat
Acepta Float, Int, Double o String de JSON → convierte a Float?:
struct Response: Codable {
@ConvertToFloat var rating: Float? // 4.5, 5, "4.5" → 4.5, 5.0, 4.5
}
Decodificador de Ruta de Clave JSON
Extrae valores JSON anidados sin analizar toda la respuesta:
// JSON: { "code": 200, "data": { "list": [{ "id": 1 }, { "id": 2 }] } }
// Extraer un solo objeto anidado
let firstItem = try await Mesh()
.setRequestMethod(.get)
.setUrlPath("/items")
.request(of: Item.self, modelKeyPath: "data.list.0")
// El decodificador KeyPath también funciona directamente:
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")
El decodificador predeterminado se autoconfigura:
keyDecodingStrategy = .convertFromSnakeCase(snake_case → camelCase)dateDecodingStrategy = .iso8601
Integración con Combine + SwiftUI
Patrón 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 en 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 en 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()
}
}
Manejo de Errores
SwiftMesh normaliza errores de red comunes en NSError amigable para el usuario:
| Código de Error | Descripción |
|---|---|
NSURLErrorNotConnectedToInternet | Sin conexión a internet |
NSURLErrorTimedOut | Tiempo de espera de solicitud agotado |
NSURLErrorCannotFindHost | Host no encontrado |
NSURLErrorCannotConnectToHost | No se puede conectar al host |
NSURLErrorNetworkConnectionLost | Conexión perdida durante la solicitud |
Todos los errores normalizados devuelven el mensaje: "No se puede conectar al servidor"
Captura errores en tu código async:
do {
let result = try await Mesh()
.setUrlPath("/api")
.request(of: Response.self)
} catch let error as NSError {
print("Error \(error.code): \(error.localizedDescription)")
}
RetryPolicy
Reintento integrado con retroceso lineal (1s, 2s, 3s...):
// Predeterminado: 3 reintentos
let policy = RetryPolicy()
// Personalizado: 5 reintentos
let policy = RetryPolicy(maxRetryCount: 5)
// Usar con solicitud
let result = try await Mesh()
.setUrlPath("/unstable-api")
.setInterceptor(policy)
.request(of: Response.self)
Registro
Habilitar al iniciar la aplicación:
// Usando el registrador unificado de Apple (os.log)
Mesh.enableLog(.log)
// Usando Swift print()
Mesh.enableLog(.print)
La salida incluye:
- Comando cURL (para reproducción fácil)
- Código de estado HTTP
- Tiempo transcurrido
- Respuesta JSON formateada
Referencia de Métodos de Configuración
Todos los métodos son encadenables (devuelven Self):
Configuración Básica
| Método | Tipo | Descripción |
|---|---|---|
.setTimeout(_:) | TimeInterval | Tiempo de espera de solicitud en segundos (predeterminado: 15) |
.setInterceptor(_:) | RequestInterceptor? | Política de reintento / manejador de credenciales |
.setRequestMethod(_:) | HTTPMethod | GET, POST, PUT, DELETE, etc. |
.setHeads(_:) | [String: String] | Encabezados por solicitud |
.setRequestEncoding(_:) | ParameterEncoding | URLEncoding, JSONEncoding, etc. |
.setUrlHost(_:) | String? | Host URL por solicitud |
.setUrlPath(_:) | String? | Ruta URL |
.setParameters(_:) | [String: Any]? | Parámetros de solicitud |
Configuración de Descarga
| Método | Tipo | Descripción |
|---|---|---|
.setDownloadType(_:) | DownloadType | .download o .resume |
(Nota: El documento original se trunca aquí; se ha traducido todo el contenido disponible.)