This is a 2 part question the first is similar to this question here: Proper usage of the Alamofire's URLRequestConvertible. But I need a little more help!
1) Do I create an enum router which implements URLRequestConvertible for each model in my model layer?
The alamofire github page provides an example of a router which i've copied here:
enum Router: URLRequestConvertible {
static let baseURLString = "http://example.com"
static var OAuthToken: String?
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)"
}
}
// MARK: URLRequestConvertible
var URLRequest: NSURLRequest {
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 .CreateUser(let parameters):
return Alamofire.ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0
case .UpdateUser(_, let parameters):
return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0
default:
return mutableURLRequest
}
}
}
When I look at this (i'm new at swift so please bear with me >_<) I see operations on a user object; they are creating a user, updating a user etc... So, if I had model objects person, company, location in my model layer, would I create a router for each model object?
2) When interacting heavily with an API, I'm used to creating a "network manager" singleton to abstract away the network layer and to hold headers and the baseurl for that API. The alamofire has a "Manager" described here:
Top-level convenience methods like Alamofire.request use a shared instance of Alamofire.Manager, which is configured with the default NSURLSessionConfiguration. As such, the following two statements are equivalent:
Alamofire.request(.GET, "http://httpbin.org/get")
let manager = Alamofire.Manager.sharedInstance
manager.request(NSURLRequest(URL: NSURL(string: "http://httpbin.org/get")))
is this manager what I should be using as my singleton? If so, how do I set the baseurl on the manager? Also, if I use this manager does / can this work together with the router construct shown above (with each model object setting it's baseurl and NSURLRquest)? If so can you provide a simple example?
I'm new to the Alamofire library and swift. So, I know there are a lot of holes in my understanding but I'm just trying to understand the best that I can! Any info helps. Thanks.
The URLRequestConvertible protocol is a lightweight way to ensure a given object can create a valid NSURLRequest . There's not really a strict set of rules or guidelines that exist forcing you to use this protocol in any particular way.
For headers that change from request to request, you can pass them directly to the request method. From the docs: Adding a custom HTTP header to a Request is supported directly in the global request method. This makes it easy to attach HTTP headers to a Request that can be constantly changing.
Alamofire provides Session , which is similar to URLSession in terms of responsibility. It helps create and manage different requests and provides common functionality for all requests, such as interception, response caching and more. You'll learn more about different types of requests later in this tutorial.
These are some really good questions. Let me attempt to answer each one in turn.
Do I create an enum router which implements URLRequestConvertible for each model in my model layer?
This is a great question and unfortunately there's no one perfect answer. There are certainly some ways that you could extend the Router
pattern to accommodate multiple object types. The first option would be to add more cases to support another object type. However, this gets hairy pretty quickly when you get more than 6 or 7 cases. Your switch statements just start to get out-of-control. Therefore, I wouldn't recommend this approach.
Another way to approach the problem is by introducing generics into the Router
.
RouterObject Protocol
protocol RouterObject {
func createObjectPath() -> String
func readObjectPath(identifier: String) -> String
func updateObjectPath(identifier: String) -> String
func destroyObjectPath(identifier: String) -> String
}
Model Objects
struct User: RouterObject {
let rootPath = "/users"
func createObjectPath() -> String { return rootPath }
func readObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
func updateObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
func destroyObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
}
struct Company: RouterObject {
let rootPath = "/companies"
func createObjectPath() -> String { return rootPath }
func readObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
func updateObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
func destroyObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
}
struct Location: RouterObject {
let rootPath = "/locations"
func createObjectPath() -> String { return rootPath }
func readObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
func updateObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
func destroyObjectPath(identifier: String) -> String { return "\(rootPath)/\(identifier)" }
}
Router
let baseURLString = "http://example.com"
var OAuthToken: String?
enum Router<T where T: RouterObject>: URLRequestConvertible {
case CreateObject(T, [String: AnyObject])
case ReadObject(T, String)
case UpdateObject(T, String, [String: AnyObject])
case DestroyObject(T, String)
var method: Alamofire.Method {
switch self {
case .CreateObject:
return .POST
case .ReadObject:
return .GET
case .UpdateObject:
return .PUT
case .DestroyObject:
return .DELETE
}
}
var path: String {
switch self {
case .CreateObject(let object, _):
return object.createObjectPath()
case .ReadObject(let object, let identifier):
return object.readObjectPath(identifier)
case .UpdateObject(let object, let identifier, _):
return object.updateObjectPath(identifier)
case .DestroyObject(let object, let identifier):
return object.destroyObjectPath(identifier)
}
}
// MARK: URLRequestConvertible
var URLRequest: NSMutableURLRequest {
let URL = NSURL(string: baseURLString)!
let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path))
mutableURLRequest.HTTPMethod = method.rawValue
if let token = OAuthToken {
mutableURLRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
switch self {
case .CreateObject(_, let parameters):
return Alamofire.ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0
case .UpdateObject(_, _, let parameters):
return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0
default:
return mutableURLRequest
}
}
}
Example Usage
func exampleUsage() {
let URLRequest = Router.CreateObject(Location(), ["address": "1234 Road of Awesomeness"]).URLRequest
Alamofire.request(URLRequest)
.response { request, response, data, error in
print(request)
print(response)
print(data)
print(error)
}
}
Now there are certainly a few tradeoffs that you have to make here. First off, your model objects need to conform to the RouterObject
protocol. Otherwise the Router
has no idea what to use for the path. Also, you'll need to make sure all your paths can be constructed with the a single identifier
. If they cannot, this design might not work. The last issue is that you cannot store the baseURL
or the OAuthToken
directly inside the Router
enum. Unfortunately, static and stored properties are not yet supported in generic enumerations.
Regardless, this would certainly be a valid way to avoid having to create a Router
for every model object.
Should the
Alamofire.Manager.sharedInstance
be used as my singletonNetworkManager
instance?
It certainly could be used in that fashion. It really depends upon your use case and how you have designed your network access. It also depends on how many different types of sessions you need. If you need background sessions and default sessions, then you probably still need the concept of a NetworkManager
that contains each custom Manager
instance. However, if you are just hitting the network with a default session, then the sharedInstance
would probably be sufficient.
How could the
baseURL
of theAlamofire
singleton be used in conjunction with theRouter
pattern?
Good question...the code below is one example of how it could be done.
Alamofire Manager extension
extension Manager {
static let baseURLString = "http://example.com"
static var OAuthToken: String?
}
Router URLRequestConvertible Updates
var URLRequest: NSMutableURLRequest {
let URL = NSURL(string: Alamofire.Manager.baseURLString)!
let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path))
mutableURLRequest.HTTPMethod = method.rawValue
if let token = Alamofire.Manager.OAuthToken {
mutableURLRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
switch self {
case .CreateObject(_, let parameters):
return Alamofire.ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0
case .UpdateObject(_, _, let parameters):
return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0
default:
return mutableURLRequest
}
}
Hopefully that helps shed some light. Best of luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With