Proper use of AlamofireConvertible URL

I read a couple of tutorials, README from @mattt, but can't figure out what is connected with them.

  • What is the proper use of URLRequestConvertible in the real world API? It seems that if I create one router by running the URLRequestConvertible protocol for all APIs, it will be barely readable. Should I create one router per endpoint?

  • The second question, most likely caused by a lack of experience with the Swift language. I cannot understand why enum used to build a router? Why don't we use a class with static methods? here is an example (from Alamofire README)

     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 } } 
  • There are two ways to pass parameters:

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

    and (let's say the user has 4 parameters)

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

    @mattt uses the first example. But this will result in hardcoded parameter names outside the router (for example, in UIViewControllers). Typo in the parameter name may result in an error.
    Other people use the 2nd option, but in this case it is completely not obvious what each parameter represents.
    What will be the correct way?

+60
ios swift alamofire
Feb 04 '15 at 23:02
source share
5 answers

Great questions. Let everyone break up every time.

What is the proper use of URLRequestConvertible in the real world API?

The URLRequestConvertible protocol is an easy way to ensure that a given object can create a valid NSURLRequest . In fact, there is no strict set of rules or guidelines that force you to use this protocol in any particular way. This is just a convenient protocol that allows other objects to maintain the state necessary for NSURLRequest to be created NSURLRequest . More information about Alamofire can be found here .

Should I create one router per endpoint?

Definitely not. This will violate the whole purpose of using Enum . Swift Enum objects are surprisingly powerful, which allows you to share a lot of overall state and include parts that are actually different. Ability to create NSURLRequest with something simple than the following, really powerful!

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

I can’t understand why the enumeration is used to build a router? Why don't we use a class with static methods?

Enumeration is used because it is a much more concise way of expressing several related objects under a common interface. All methods are distributed among all cases. If you used static methods, there must be a static method for each method for each method. Or you will have to use Obj-C style stroke inside the object. Here is a brief example of what I mean.

 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.Method { 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)" } } } 

To get a method from any of the different endpoints, you can call the same method without having to pass any parameters to determine what type of endpoint you are looking for, it is already being processed by the case you selected.

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

Or, if you want to get the path, the same types of calls.

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

Now try using the same approach using static methods.

 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 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. If you do not have many properties or functions that include cases, then an enumeration does not provide many advantages over a structure. This is just an alternative approach with different syntactic sugar.

Enumerations can maximize reuse of state and code. Bound values ​​also allow you to do some really powerful things, such as grouping objects that are somewhat similar but have incredibly different requirements ... for example, NSURLRequest creation.

What is the correct way to create parameters for enumerations to improve readability? (had to crush it together)

This is a terrific question. You have already identified two possible options. Let me add a third, which can satisfy your needs a little better.

 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) 

In cases where you have related values, I think it may be useful to add explicit names for all the values ​​in the tuple. It really helps build context. The disadvantage is that you have to override these values ​​in switch statements like this.

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

As long as it gives you a pleasant, consistent context, it becomes quite verbose. These are your three options at the moment in Swift, which one to use depends on your use case.




Update

With the release of 🔥🔥 Alamofire 4.0 🔥🔥, URLRequestConvertible can now be much smarter and can also throw. We have added full Alamofire support for handling invalid requests and creating reasonable errors using response handlers. This new system is described in detail in our README .

+90
Feb 06 '15 at 7:07
source share

Why don't you try using SweetRouter . This will help you remove the entire template that you have when declaring the router, and also supports things like multiple environments, and your code will be really understandable.

Here is an example of a router with a sweet 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 } 

And here is how you use it:

 Alamofire.request(Router<Api>(at: .me)) Alamofire.request(Router<Api>(.test, at: .auth)) Alamofire.request(Router<Api>(.production, at: .posts(for: Date()))) 
+6
Aug 30 '17 at 10:11
source share

I found a way to work with it, I created a class with a Router in it: inherit classes from the request

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

file request Contacts.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 } } } } 

therefore, the son class will receive the parent parameters from the parent, and you can even use Route.login in any son. still don't know if there is a way to get a short URLRequest, so I don't need to set the parameters again and again

+4
May 20 '15 at 20:36
source share

Types using the URLRequestConvertible protocol can be used to create URL requests.

Here is an example taken from 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 } } 

and we can use this ImmageRouter as follows:

 Alamofire.request(ImaggaRouter.Tags(contentID)) .responseJSON{ response in 
+3
Apr 03 '16 at 0:04
source share

Here is the updated enum Router in Swift 3, which is recommended on Alamofire Github . I hope you find this useful in terms of properly implementing the router with 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 } } 
+3
Nov 17 '16 at 18:04
source share



All Articles