swiftyjson example ios swift alamofire

ios - example - Uso adecuado del URLRequestConquestible de Alamofire



swiftyjson alamofire (5)

He leído un par de tutoriales, README de @mattt, pero no puedo entender un par de cosas.

  1. ¿Cuál es el uso adecuado de URLRequestConvertible en la API del mundo real? Parece que si voy a crear un enrutador implementando el protocolo URLRequestConvertible para todas las API, será apenas legible. ¿Debo crear un enrutador por punto final?

  2. Segunda pregunta probablemente causada por la falta de experiencia con el lenguaje Swift. No puedo entender por qué se usa enum para construir el enrutador? ¿Por qué no usamos la clase con métodos estáticos? Aquí hay un ejemplo (del archivo README de Alamofire)

    enum Router: URLRequestConvertible { static let baseURLString = "http://example.com" static let perPage = 50 case Search(query: String, page: Int) // MARK: URLRequestConvertible var URLRequest: NSURLRequest { let (path: String, parameters: [String: AnyObject]?) = { switch self { case .Search(let query, let page) where page > 1: return ("/search", ["q": query, "offset": Router.perPage * page]) case .Search(let query, _): return ("/search", ["q": query]) } }() let URL = NSURL(string: Router.baseURLString)! let URLRequest = NSURLRequest(URL: URL.URLByAppendingPathComponent(path)) let encoding = Alamofire.ParameterEncoding.URL return encoding.encode(URLRequest, parameters: parameters).0 } }

  3. Hay 2 formas de pasar parámetros:

    case CreateUser([String: AnyObject]) case ReadUser(String) case UpdateUser(String, [String: AnyObject]) case DestroyUser(String)

    y (digamos que el usuario tiene 4 parámetros)

    case CreateUser(String, String, String, String) case ReadUser(String) case UpdateUser(String, String, String, String, String) case DestroyUser(String)

    @mattt está usando el primero en el ejemplo. Pero eso conducirá a nombres de parámetros de "codificación rígida" fuera del enrutador (por ejemplo, en UIViewControllers). El error tipográfico en el nombre del parámetro puede provocar un error.
    Otras personas están usando la segunda opción, pero en ese caso no es obvio en absoluto lo que representa cada parámetro.
    ¿Cuál será la forma correcta de hacerlo?


¿Por qué no intentas usar SweetRouter ? Le ayudará a eliminar todas las repeticiones que tiene al declarar un enrutador y también admite cosas como entornos múltiples y su código será realmente legible.

Aquí hay un ejemplo del enrutador con enrutador dulce:

struct Api: EndpointType { enum Environment: EnvironmentType { case localhost case test case production var value: URL.Environment { switch self { case .localhost: return .localhost(8080) case .test: return .init(IP(126, 251, 20, 32)) case .production: return .init(.https, "myproductionserver.com", 3000) } } } enum Route: RouteType { case auth, me case posts(for: Date) var route: URL.Route { switch self { case .me: return .init(at: "me") case .auth: return .init(at: "auth") case let .posts(for: date): return URL.Route(at: "posts").query(("date", date), ("userId", "someId")) } } } static let current: Environment = .localhost }

Y así es como lo usarías:

Alamofire.request(Router<Api>(at: .me)) Alamofire.request(Router<Api>(.test, at: .auth)) Alamofire.request(Router<Api>(.production, at: .posts(for: Date())))


Aquí está el enum Router actualizado en Swift 3, que se recomienda en here . Espero que lo encuentre útil en términos de cómo implementar correctamente un enrutador con URLRequestConvertible .

import Alamofire enum Router: URLRequestConvertible { case createUser(parameters: Parameters) case readUser(username: String) case updateUser(username: String, parameters: Parameters) case destroyUser(username: String) static let baseURLString = "https://example.com" var method: HTTPMethod { switch self { case .createUser: return .post case .readUser: return .get case .updateUser: return .put case .destroyUser: return .delete } } var path: String { switch self { case .createUser: return "/users" case .readUser(let username): return "/users//(username)" case .updateUser(let username, _): return "/users//(username)" case .destroyUser(let username): return "/users//(username)" } } // MARK: URLRequestConvertible func asURLRequest() throws -> URLRequest { let url = try Router.baseURLString.asURL() var urlRequest = URLRequest(url: url.appendingPathComponent(path)) urlRequest.httpMethod = method.rawValue switch self { case .createUser(let parameters): urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters) case .updateUser(_, let parameters): urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters) default: break } return urlRequest } }


Encontré una manera de trabajar con él, creé una clase con el enrutador: heredar clases de una solicitud

archivo request.swift

class request{ func login(user: String, password: String){ /*use Router.login(params)*/ } /*...*/ enum Router: URLRequestConvertible { static let baseURLString = "http://example.com" static let OAuthToken: String? case Login([String: AnyObject]) /*...*/ var method: Alamofire.Method { switch self { case .Login: return .POST /*...*/ } var path: String { switch self { case .Login: return "/login" /*...*/ } } var URLRequest: NSURLRequest { switch self { case .Login(let parameters): return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0 /*...*/ default: return mutableURLRequest } } } }

file requestContacts.swift

class requestContacts: api{ func getUser(id: String){ /*use Router.getUser(id)*/ } /*...*/ enum Router: URLRequestConvertible { case getUser(id: String) case setUser([String: AnyObject]) var method: Alamofire.Method { switch self { case .getUser: return .GET case .setUser: return .POST /*...*/ } } var path: String { switch self { case .getUser(id: String): return "/user/(id)/" case .setUser(id: String): return "/user/" /*...*/ } } // MARK: URLRequestConvertible var URLRequest: NSURLRequest { //use same baseURLString seted before let URL = NSURL(string: Router.baseURLString)! let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path)) mutableURLRequest.HTTPMethod = method.rawValue if let token = Router.OAuthToken { mutableURLRequest.setValue("Bearer /(token)", forHTTPHeaderField: "Authorization") } switch self { /*...*/ case .setUser(let parameters): return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0 default: //for GET methods, that doesent need more return mutableURLRequest } } } }

para que la clase hijo obtenga los parámetros de Router del padre, e incluso puede usar Route.login en cualquier hijo. aún así, no sé si hay una manera de obtener una URLRequest corta, por lo que no necesito establecer parámetros una y otra vez


Grandes preguntas Analicemos cada uno individualmente.

¿Cuál es el uso adecuado de URLRequestConvertible en la API del mundo real?

El protocolo URLRequestConvertible es una forma ligera de garantizar que un objeto determinado pueda crear una NSURLRequest válida. No existe realmente un conjunto estricto de reglas o pautas que lo obliguen a usar este protocolo de ninguna manera en particular. Es simplemente un protocolo de conveniencia para permitir que otros objetos almacenen el estado requerido para crear correctamente NSURLRequest . Puede encontrar más información relacionada con Alamofire here .

¿Debo crear un enrutador por punto final?

Definitivamente no. Eso derrotaría todo el propósito de usar una Enum . Los objetos Swift Enum son increíblemente potentes, lo que le permite compartir una gran cantidad de estado común y activar las partes que realmente son diferentes. ¡Poder crear una NSURLRequest con algo tan simple como lo siguiente es realmente poderoso!

let URLRequest: NSURLRequest = Router.ReadUser("cnoon")

No puedo entender por qué se usa enum para construir el enrutador? ¿Por qué no usamos la clase con métodos estáticos?

Se está utilizando una enumeración porque es una forma mucho más concisa de expresar múltiples objetos relacionados en una interfaz común. Todos los métodos se comparten entre todos los casos. Si usó métodos estáticos, tendría que tener un método estático para cada caso para cada método. O tendría que usar una enumeración de estilo Obj-C dentro del objeto. Aquí hay un ejemplo rápido de lo que quiero decir.

enum Router: URLRequestConvertible { static let baseURLString = "http://example.com" case CreateUser([String: AnyObject]) case ReadUser(String) case UpdateUser(String, [String: AnyObject]) case DestroyUser(String) var method: Alamofire.HTTPMethod { switch self { case .CreateUser: return .post case .ReadUser: return .get case .UpdateUser: return .put case .DestroyUser: return .delete } } var path: String { switch self { case .CreateUser: return "/users" case .ReadUser(let username): return "/users//(username)" case .UpdateUser(let username, _): return "/users//(username)" case .DestroyUser(let username): return "/users//(username)" } } }

Para obtener el método de cualquiera de los diferentes puntos finales, puede llamar al mismo método sin tener que pasar ningún parámetro para definir qué tipo de punto final está buscando, ya lo maneja el caso que seleccione.

let createUserMethod = Router.CreateUser.method let updateUserMethod = Router.UpdateUser.method

O si desea obtener la ruta, los mismos tipos de llamadas.

let updateUserPath = Router.UpdateUser.path let destroyUserPath = Router.DestroyUser.path

Ahora intentemos el mismo enfoque usando métodos estáticos.

struct Router: URLRequestConvertible { static let baseURLString = "http://example.com" static var method: Method { // how do I pick which endpoint? } static func methodForEndpoint(endpoint: String) -> Method { // but then I have to pass in the endpoint each time // what if I use the wrong key? // possible solution...use an Obj-C style enum without functions? // best solution, merge both concepts and bingo, Swift enums emerge } static var path: String { // bummer...I have the same problem in this method too. } static func pathForEndpoint(endpoint: String) -> String { // I guess I could pass the endpoint key again? } static var pathForCreateUser: String { // I''ve got it, let''s just create individual properties for each type return "/create/user/path" } static var pathForUpdateUser: String { // this is going to get really repetitive for each case for each method return "/update/user/path" } // This approach gets sloppy pretty quickly }

NOTA: Si no tiene muchas propiedades o funciones que activen los casos, una enumeración no presenta muchas ventajas sobre una estructura. Es simplemente un enfoque alternativo con diferentes azúcares sintácticos.

Las enumeraciones pueden maximizar el estado y la reutilización del código. Los valores asociados también le permiten hacer algunas cosas realmente poderosas como agrupar objetos que son algo similares, pero tienen requisitos increíblemente diferentes ... como la creación de NSURLRequest .

¿Cuál es la forma correcta de construir parámetros para casos de enumeración para mejorar la legibilidad? (Tuve que machacar esto juntos)

Esa es una pregunta fabulosa. Ya ha presentado dos posibles opciones. Permítanme agregar un tercero que puede satisfacer sus necesidades un poco mejor.

case CreateUser(username: String, firstName: String, lastName: String, email: String) case ReadUser(username: String) case UpdateUser(username: String, firstName: String, lastName: String, email: String) case DestroyUser(username: String)

En los casos en que tiene valores asociados, creo que puede ser útil agregar nombres explícitos para todos los valores en la tupla. Esto realmente ayuda a construir el contexto. La desventaja es que debe volver a declarar esos valores en sus declaraciones de cambio de esta manera.

static var method: String { switch self { case let CreateUser(username: username, firstName: firstName, lastName: lastName, email: email): return "POST" default: return "GET" } }

Si bien esto le da un contexto agradable y consistente, se vuelve bastante detallado. Esas son sus tres opciones en este momento en Swift, cuál es la correcta para usar dependiendo de su caso de uso.

Actualizar

Con el lanzamiento de 🔥🔥 Alamofire 4.0 🔥🔥, el URLRequestConvertible ahora puede ser MUCHO más inteligente y también puede lanzarse. Hemos agregado soporte completo a Alamofire para manejar solicitudes no válidas y generar errores sensibles a través de los manejadores de respuestas. Este nuevo sistema está documentado en detalle en nuestro README .


Los tipos que adoptan el protocolo URLRequestConvertible se pueden usar para construir solicitudes de URL.

Aquí hay un ejemplo tomado de www.raywenderlich.com

public enum ImaggaRouter : URLRequestConvertible{ static let baseURL = "http://api.imagga.com/v1" static let authenticationToken = "XAFDSADGDFSG DAFGDSFGL" case Content, Tags(String), Colors(String) public var URLRequest: NSMutableURLRequest { let result: (path: String, method: Alamofire.Method, parameters: [String: AnyObject]) = { switch self { case .Content: return ("/content", .POST, [String: AnyObject]()) case .Tags(let contentID): let params = [ "content" : contentID ] return ("/tagging", .GET, params) case .Colors(let contentID): let params = [ "content" : contentID, "extract_object_colors" : NSNumber(int: 0) ] return ("/colors", .GET, params) } }() let URL = NSURL(string: ImaggaRouter.baseURL)! let URLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(result.path)) URLRequest.HTTPMethod = result.method.rawValue URLRequest.setValue(ImaggaRouter.authenticationToken, forHTTPHeaderField: "Authorization") URLRequest.timeoutInterval = NSTimeInterval(10 * 1000) let encoding = Alamofire.ParameterEncoding.URL return encoding.encode(URLRequest, parameters: result.parameters).0 } }

y podemos usar este ImmageRouter de la siguiente manera:

Alamofire.request(ImaggaRouter.Tags(contentID)) .responseJSON{ response in