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.
-
¿Cuál es el uso adecuado de
URLRequestConvertible
en la API del mundo real? Parece que si voy a crear un enrutador implementando el protocoloURLRequestConvertible
para todas las API, será apenas legible. ¿Debo crear un enrutador por punto final? -
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 } }
-
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