El uso correcto de la Alamofire del URLRequestConvertible

Me he leido un par de tutoriales, LÉAME de @mattt, pero no puede averiguar par de cosas.

  1. ¿Cuál es el uso adecuado de los URLRequestConvertible en el mundo real de la API? Parece como si voy a crear un router mediante la implementación de URLRequestConvertible protocolo para todas las API – será apenas legible. Debo crear un Router por extremo?

  2. Segunda pregunta la causa más probable de la falta de experiencia con lenguaje Swift. No puedo entender por qué enum se utiliza para la construcción de un router? Por qué no utilizamos la clase con los métodos estáticos?
    he aquí un ejemplo (de Alamofire LÉAME)

    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 (decir 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 es el uso de la primera persona del ejemplo. Pero que llevará a «codificar» de los parámetros de los nombres de fuera del router (por ejemplo, en UIViewControllers).
    Error tipográfico en el nombre del parámetro puede conducir a error.

    Otras personas están usando la opción 2, pero en ese caso no es obvio en absoluto lo que cada parámetro representa.

    ¿Cuál será la manera correcta de hacerlo?

InformationsquelleAutor OgreSwamp | 2015-02-04

5 Kommentare

  1. 103

    Grandes preguntas. Analicemos cada uno de ellos individualmente.

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

    La URLRequestConvertible protocolo es una forma ligera para garantizar un objeto dado puede crear una válida NSURLRequest. Realmente no hay un estricto conjunto de reglas o directrices que existen, obligando a utilizar este protocolo de ninguna manera en particular. Es simplemente una conveniencia protocolo para permitir a otros objetos para almacenar el estado requiere para crear correctamente el NSURLRequest. Algo más de información relativa a Alamofire se puede encontrar aquí.

    Debo crear un Router por extremo?

    Definitivamente no. Que derrota el propósito de usar un Enum. Swift de la Enumeración de los objetos son increíblemente potente, lo que permite compartir una gran cantidad de estado común, y el interruptor de las partes que realmente diferentes. Ser capaz de crear un NSURLRequest con algo tan simple como la siguiente es realmente poderoso!

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

    No puedo entender por qué enum se utiliza para la construcción de un router? Por qué no utilizamos la clase con los métodos estáticos?

    Una enumeración se utiliza porque es una forma mucho más concisa forma de expresar múltiples objetos relacionados bajo una interfaz común. Todos los métodos son compartidos entre todos los casos. Si usted utiliza los métodos estáticos, usted tendría que tener un método estático para cada caso, para cada método. O tendría que utilizar un Obj-C estilo de la enumeración en el interior del objeto. Aquí está 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 parámetros, usted puede llamar el mismo método sin tener que pasar ningún parámetro para definir qué tipo de extremo de que usted está buscando, ya está manejado por el caso de que usted seleccione.

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

    O si desea obtener la ruta de acceso, el mismo tipo de llamadas.

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

    Ahora vamos a tratar el mismo enfoque el uso de 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 usted no tiene muchas propiedades o funciones que el interruptor de los casos, a continuación una enumeración no presenta muchas ventajas sobre una estructura. Es simplemente un enfoque alternativo con diferentes azúcar sintáctico.

    Las enumeraciones pueden maximizar estado y la reutilización de código. Los valores asociados también permite hacer algo realmente potente cosas como la agrupación de objetos que son algo similares, pero increíblemente diferentes requisitos…como NSURLRequest creación.

    ¿Cuál es la manera correcta de construir parámetros para la enumeración de los casos para mejorar la legibilidad? (había de pisar aquí)

    Esa es una excelente pregunta. Usted ya ha presentado dos posibles opciones. Permítanme añadir una tercera, que pueden adaptarse a 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 donde se tienen asociados valores, creo que puede ser útil añadir nombres explícitos para todos los valores de la tupla. Esto realmente ayuda a construir el contexto. La desventaja es que tienes entonces para volver a declarar esos valores en sus sentencias switch como así.

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

    Mientras este le da un bonito, consistente contexto, se pone muy detallado. Esas son las tres opciones en el momento en Swift, que es el correcto para utilizar depende de su caso de uso.


    Actualización

    Con el lanzamiento de 🔥🔥 Alamofire 4.0 🔥🔥, el URLRequestConvertible ahora puede ser MUCHO más inteligente y también puede lanzar. Hemos añadido soporte completo en Alamofire para el manejo de las solicitudes no válidas y generar sensible errores a través de la respuesta de los controladores. Este nuevo sistema está documentado en detalle en nuestra LÉAME.

    • Gracias. Sólo una pregunta con respecto a su respuesta acerca de un router vs edificio router por extremo (por ejemplo, CRUD ejemplo de Alamofire página). No crees que si he de decir 5 estaciones, cada una tiene 3-4 métodos, – que es de 15-20 case declaraciones. Que se parece a un gran método para mí. No estoy seguro de si lo que llevará a un código legible…
    • Respecto a la segunda respuesta (enum vs métodos estáticos) – el agujero punto para mí aquí es para ocultar una aplicación dentro de enum/clase. No necesito saber los métodos o caminos fuera de ella. Quiero llamar Router.createUser("[email protected]", "....") y tienen un bloque para la interpretación de los Resultados para el servidor. Todos los detalles (métodos, caminos, API raíz etc) puede ser privado de Router – eso está bien.
    • El problema que veo aquí (y la razón de esta pregunta es que yo tengo no sólo /users extremo con 4 métodos. Tengo otros extremos, como /tickets, /venues etc. Tengo alrededor de 4 de ellos ahora, y potencialmente incluso más. Así que en lugar de niza switch declaración que voy a tener un switch con 20 casos. No estoy seguro de si es bueno… Es un poco código olor para mí.
    • Todos perfectamente válidos puntos. Todos estamos en el mismo barco pidiendo exactamente la misma pregunta. A tu primer comentario, que solo tienen un solo interruptor en cada función. También, algunas de las funciones pueden devolver los mismos valores que para que usted no tiene un interruptor/conjunto de casos. Esto depende totalmente de la situación. A su segundo, si eso es todo lo que vas a usar el Enum para, a continuación, me gustaría decir que una estructura o enum trabajo sería prácticamente el mismo. Realmente no hay ventajas sobre uno o el otro. Te gustaría tener múltiples casos o varias funciones.
    • A tu último comentario, no creo que te gustaría 20 cosas diferentes extremos en una sola enumeración si también había un montón de funciones. Sus sentencias switch sería tan largo no sería muy legible. Definitivamente código de olor en ese momento. Para mí, una vez que recibe más de 5 o 6 casos en los conmutadores, realmente empiezas a perder legibilidad.
    • He añadido una NOTA en la estructura / enum comparación de la sección de la respuesta detallando el caso de que usted no tiene muchas propiedades o métodos.
    • Permítanos continuar esta discusión en el chat.
    • A tu último comentario @cnoon, (he leído los comentarios anteriores) que usted está diciendo que (utilizando tu ejemplo de CRUD de usuario del router), si tengo algunas peticiones que pertenece a diferentes contextos, como la Solicitud de los mensajes de twitter y el Usuario CRUD, de los que serán los dos separados de los routers?
    • Sí, eso es correcto @RenanKosicki. Ciertamente llegar a un punto de no retorno cuando usted tiene muchos de los casos en un Router enum. Dividirlos en grupos lógicos ciertamente es la más deseable de diseño.
    • Me gustaría poder añadir esta respuesta a la lista de favoritos!
    • Con respecto al diseño, donde tenemos varios Routers para diferentes contextos, ¿cómo puedo volver a usar el mismo baseURL y authorizationHeader para todos los routers?
    • Preguntando exactamente lo mismo.
    • ¿Qué acerca de los casos en que se desea inyectar datos (url base, o token) a dicho router? Usted no puede hacer eso con una enumeración. El enfoque que sería mejor que aquí?

  2. 7

    ¿Por qué no intenta utilizar SweetRouter. Esto le ayudará a eliminar todas las repetitivo que usted tiene cuando se declara un Router y también es compatible con las cosas tales como múltiples entornos y su código será realmente readible.

    He aquí un ejemplo de los Router con dulce router:

    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 aquí es cómo usted podría utilizarlo:

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

    Aquí la fecha enum Router en Swift 3, que se recomienda en Alamofire del Github. Espero que les sea útil en términos de cómo implementar correctamente un Router 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
        }
    }
  4. 4

    He encontrado una manera de trabajar con él, he creado una Clase con el Router en el mismo:
    heredar de las clases a partir de una solicitud de

    solicitud de archivo.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
                }
            }
        }
    }

    así que el hijo de la clase recibirá paramethers de Router desde el padre, y usted puede incluso utilizar la Ruta.inicio de sesión en cualquier hijo.
    aún así, no sé si hay una manera de obtener a corto URLRequest, por lo que no necesito para configurar los parámetros de nuevo y de nuevo

    • Hola, estoy tratando de hacer como usted dice en su respuesta, pero cuando trato de usar el método POST me sigue apareciendo el método GET de la respuesta. Por ejemplo: Cuando estoy al acceder a la url «/usuarios» en lugar de crear el usuario con el método POST, me estoy haciendo la lista de todos los usuarios, que es el método GET de la respuesta. Alguna idea de por qué está sucediendo esto? Parece incluso establecer el método con mutableURLRequest.HTTPMethod = method.rawValue no cambia nada.
    • lo enum hizo usted acceso?? usted tiene que seleccionar el enum para un POST, y establecer un puesto para el que el valor de enumeración, aquí el post es del Router.setUser(…)
    • Puede usted comprobar mi pregunta aquí en tanto? No puedo proporcionar todos los detalles. Aquí está el enlace: Question
  5. 3

    Tipos de adopción de la URLRequestConvertible protocolo puede ser utilizado para la construcción de las solicitudes de direcciones URL.

    He aquí un ejemplo tomado de la http://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 como las siguientes:

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

Kommentieren Sie den Artikel

Bitte geben Sie Ihren Kommentar ein!
Bitte geben Sie hier Ihren Namen ein

Pruebas en línea