web-dev-qa-db-fra.com

Utilisation appropriée de l'URLRequestConvertible d'Alamofire

J'ai lu quelques tutoriels, README de @mattt mais je ne peux pas comprendre deux ou trois choses.

  1. Quelle est la bonne utilisation de URLRequestConvertible dans une API du monde réel? Il semble que si je crée un routeur en implémentant le protocole URLRequestConvertible pour toutes les API, il sera à peine lisible. Devrais-je créer un routeur par point d'extrémité?

  2. Deuxième question probablement due au manque d'expérience avec Swift langage. Je n'arrive pas à comprendre pourquoi enum est utilisé pour la construction d'un routeur. Pourquoi n'utilisons-nous pas la classe statique? méthodes? voici un exemple (tiré du README d’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. Il y a 2 façons de passer des paramètres:

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

    et (disons que l'utilisateur a 4 paramètres)

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

    @att utilise le premier dans l'exemple. Mais cela conduira à "coder en dur" les noms des paramètres en dehors du routeur (par exemple dans UIViewControllers). Une faute de frappe dans le nom du paramètre peut entraîner une erreur.
    D'autres personnes utilisent la 2ème option, mais dans ce cas, la signification de chaque paramètre n'est pas évidente.
    Quelle sera la bonne façon de le faire?

76
OgreSwamp

Excellentes questions. Décomposons chacun individuellement.

Quelle est la bonne utilisation de URLRequestConvertible dans les API du monde réel?

Le protocole URLRequestConvertible est un moyen léger de s’assurer qu’un objet donné peut créer un NSURLRequest valide. Il n'y a pas vraiment un ensemble strict de règles ou de directives qui vous obligent à utiliser ce protocole de manière particulière. C'est simplement un protocole de commodité permettant à d'autres objets de stocker l'état requis pour créer correctement le NSURLRequest. Quelques informations supplémentaires sur Alamofire peuvent être trouvées ici .

Devrais-je créer un routeur par point d'extrémité?

Définitivement pas. Cela irait à l'encontre de l'objectif d'utiliser un Enum. Swift Les objets Enum sont incroyablement puissants. Ils vous permettent de partager une grande quantité d’états communs et d’allumer les parties réellement différentes. Possibilité de créer un NSURLRequest avec quelque chose d'aussi simple. comme ce qui suit est vraiment puissant!

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

Je n'arrive pas à comprendre pourquoi enum est utilisé pour construire un routeur? Pourquoi n'utilisons-nous pas la classe avec des méthodes statiques?

Une énumération est utilisée car il s'agit d'un moyen beaucoup plus concis d'exprimer plusieurs objets liés sous une interface commune. Toutes les méthodes sont partagées entre tous les cas. Si vous utilisiez des méthodes statiques, vous auriez besoin d'une méthode statique pour chaque cas pour chaque méthode. Ou vous devrez utiliser une énumération de style Obj-C à l'intérieur de l'objet. Voici un exemple rapide de ce que je veux dire.

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)"
        }
    }
}

Pour obtenir la méthode de l'un des points de terminaison différents, vous pouvez appeler la même méthode sans avoir à transmettre de paramètres permettant de définir le type de point de terminaison que vous recherchez. Il est déjà géré par le cas sélectionné.

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

Ou si vous voulez obtenir le chemin, les mêmes types d'appels.

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

Essayons maintenant la même approche en utilisant des méthodes statiques.

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
}

NOTE: Si vous n'avez pas beaucoup de propriétés ou de fonctions qui activent les cas, une énumération ne présente pas beaucoup d'avantages par rapport à une structure. C'est simplement une approche alternative avec différents sucres syntaxiques.

Les enums peuvent maximiser l'état et la réutilisation du code. Les valeurs associées vous permettent également de faire des choses très puissantes telles que le regroupement d'objets qui sont assez similaires, mais qui ont des exigences incroyablement différentes ... telles que NSURLRequest creation.

Quelle est la bonne façon de construire des paramètres pour les cas enum afin d’améliorer la lisibilité? (a dû écraser celui-ci ensemble)

C'est une excellente question. Vous avez déjà présenté deux options possibles. Permettez-moi d’en ajouter un troisième qui conviendra peut-être mieux à vos besoins.

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)

Dans les cas où vous avez associé des valeurs, il peut être utile d’ajouter des noms explicites pour toutes les valeurs du tuple. Cela aide vraiment à construire le contexte. L'inconvénient est que vous devez alors redéclarer ces valeurs dans vos instructions de commutateur comme suit.

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

Bien que cela vous donne un contexte agréable et cohérent, il devient assez verbeux. Ce sont vos trois options pour le moment dans Swift, laquelle est la bonne à utiliser dépend de votre cas d'utilisation.


Mise à jour

Avec la sortie de ???????? Alamofire 4.0, le URLRequestConvertible peut maintenant être BEAUCOUP plus intelligent et peut également être lancé. Nous avons ajouté à Alamofire un support complet pour la gestion des requêtes non valides et la génération d’erreurs sensibles via les gestionnaires de réponses. Ce nouveau système est documenté en détail dans notre README .

103
cnoon

Pourquoi n'essayez-vous pas d'utiliser SweetRouter . Cela vous aidera à supprimer tout ce que vous avez en déclarant un routeur. Il prend également en charge des éléments tels que plusieurs environnements et votre code sera vraiment lisible.

Voici un exemple de routeur avec routeur doux:

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
}

Et voici comment vous l'utiliseriez:

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

Voici la mise à jour enum Router in Swift 3, ce qui est recommandé Github d’Alamofire . J'espère que vous le trouverez utile pour mettre correctement en œuvre un routeur avec 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
    }
}
6
AndreasLukas

J'ai trouvé un moyen de l'utiliser, j'ai créé une classe avec le routeur: hériter des classes d'une requête

demande de fichier.

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
            }
        }
    }
}

fichier 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
            }
        }
    }
}

ainsi, la classe fils obtiendra les paramètres de Router du parent, et vous pourrez même utiliser Route.login dans n’importe quel fils. toujours, ne sais pas s’il existe un moyen d’obtenir un court URLRequest, donc je n’ai pas besoin de définir des paramètres encore et encore

Les types adoptant le protocole URLRequestConvertible peuvent être utilisés pour construire des requêtes URL.

Voici un exemple tiré 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
  }
}

et nous pouvons utiliser cet ImmageRouter comme suit:

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