Grandes preguntas. Analicemos cada uno individualmente.
¿Cuál es el uso adecuado de URLRequestConvertible en la API del mundo real?
El URLRequestConvertible
protocolo es una forma ligera de garantizar que un objeto dado pueda crear un archivo NSURLRequest
. Realmente no existe un conjunto estricto de reglas o pautas que lo obliguen a usar este protocolo de una manera particular. Es simplemente un protocolo de conveniencia para permitir que otros objetos almacenen el estado requerido para crear correctamente el NSURLRequest
. Puede encontrar más información relacionada con Alamofire aquí .
¿Debo crear un enrutador por punto final?
Definitivamente no. Eso frustraría todo el propósito de usar un Enum
. Los objetos Swift Enum son increíblemente poderosos y te permiten compartir una gran cantidad de estados comunes y activar las partes que realmente son diferentes. ¡Poder crear un 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 enrutadores. ¿Por qué no usamos clases con métodos estáticos?
Se utiliza una enumeración porque es una forma mucho más concisa de expresar varios 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. He aquí 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 {
}
static func methodForEndpoint(endpoint: String) -> Method {
}
static var path: String {
}
static func pathForEndpoint(endpoint: String) -> String {
}
static var pathForCreateUser: String {
return "/create/user/path"
}
static var pathForUpdateUser: String {
return "/update/user/path"
}
}
NOTA: Si no tiene muchas propiedades o funciones que activen los casos, entonces una enumeración no presenta muchas ventajas sobre una estructura. Es simplemente un enfoque alternativo con diferente azúcar sintáctico.
Las enumeraciones pueden maximizar la reutilización del código y el estado. Los valores asociados también le permiten hacer cosas realmente poderosas como agrupar objetos que son algo similares, pero tienen requisitos increíblemente diferentes ... como la NSURLRequest
creación.
¿Cuál es la forma correcta de construir parámetros para casos de enumeración para mejorar la legibilidad? (tuve que triturar este juntos)
Esa es una excelente pregunta. Ya ha presentado dos opciones posibles. Permítanme agregar un tercio 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 los que tenga 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 luego debe volver a declarar esos valores en sus declaraciones de cambio de esa 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 brinda 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 depende de su caso de uso.
Actualizar
Con el lanzamiento de 🔥🔥 Alamofire 4.0 🔥🔥, URLRequestConvertible
ahora puede ser MUCHO más inteligente y también puede lanzar. Hemos agregado soporte completo en Alamofire para manejar solicitudes no válidas y generar errores sensibles a través de los controladores de respuesta. Este nuevo sistema está documentado en detalle en nuestro README .
case
declaraciones. Eso me parece un método enorme. No estoy seguro de si eso llevará al código legible ...Router.createUser("[email protected]", "....")
y tener un bloqueo para interpretar Resultados para el servidor. Todos los detalles (métodos, rutas, raíz de la API, etc.) pueden ser privados para el enrutador, eso está bien.Aquí está la actualización
enum Router
de Swift 3, que se recomienda en Github de Alamofire . Espero que lo encuentre útil en términos de cómo implementar correctamente un enrutador conURLRequestConvertible
.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 } }
fuente
¿Por qué no intentas usar SweetRouter ? Le ayudará a eliminar todo el texto estándar que tiene al declarar un enrutador y también admite cosas como múltiples entornos 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ía:
Alamofire.request(Router<Api>(at: .me)) Alamofire.request(Router<Api>(.test, at: .auth)) Alamofire.request(Router<Api>(.production, at: .posts(for: Date())))
fuente
Encontré una manera de trabajar con él, creé una clase con el enrutador: heredar clases de una solicitud
file 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 } } } }
archivo 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 } } } }
por lo que la clase del hijo obtendrá 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
fuente
mutableURLRequest.HTTPMethod = method.rawValue
sin cambios.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
fuente
en lugar de caso UpdateUser (nombre de usuario: String, firstName: String, lastName: String, email: String)
harías
struct UserAttributes { let username: String .... }
y alimenta ESE objeto modelo como un parámetro en lugar de un grupo de cadenas ilegibles sin nombre
case UpdateUser (parámetros: UserAttributes)
fuente