Proper model for multiple Alamofire requests for multiple websites
There are many ways to design this type of abstraction. I tend to lean towards simplicity as much as possible in my architectural designs if possible. A great pattern here is to use a Service
object with class methods to handle calling your different services, parsing the result and calling a success or failure closure.
You can also use a completion handler that doesn't split the success and failure into two things, but then you need to handle the failure or success in your caller objects which I don't really like. Here's an example of the Service
design in action.
FirstNewsService
import Alamofire
struct News {
let title: String
let content: String
let date: NSDate
let author: String
}
class FirstNewsService {
typealias NewsSuccessHandler = ([News]) -> Void
typealias NewsFailureHandler = (NSHTTPURLResponse?, AnyObject?, NSError?) -> Void
// MARK: - Fetching News Methods
class func getNews(#success: NewsSuccessHandler, failure: NewsFailureHandler) {
login(
success: { apiKey in
FirstNewsService.fetch(
apiKey: apiKey,
success: { news in
success(news)
},
failure: { response, json, error in
failure(response, json, error)
}
)
},
failure: { response, json, error in
failure(response, json, error)
}
)
}
// MARK: - Private - Helper Methods
private class func login(#success: (String) -> Void, failure: (NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
let request = Alamofire.request(.GET, "login/url")
request.responseJSON { _, response, json, error in
if let error = error {
failure(response, json, error)
} else {
// NOTE: You'll need to parse here...I would suggest using SwiftyJSON
let apiKey = "12345678"
success(apiKey)
}
}
}
private class func fetch(
#apiKey: String,
success: ([News]) -> Void,
failure: (NSHTTPURLResponse?, AnyObject?, NSError?) -> Void)
{
let request = Alamofire.request(.GET, "fetch/url")
request.responseJSON { _, _, json, error in
if let error = error {
failure(response, json, error)
} else {
// NOTE: You'll need to parse here...I would suggest using SwiftyJSON
let news = [News]()
success(news)
}
}
}
}
Inside a View Controller
override func viewDidLoad() {
super.viewDidLoad()
FirstNewsService.getNews(
success: { news in
// Do something awesome with that news
self.tableView.reloadData()
},
failure: { response, json, error in
// Be flexible here...do you want to retry, pull to refresh, does it matter what the response status code was?
println("Response: \(response)")
println("Error: \(error)")
}
)
}
Feel free to mod the design however you like to tailor it to your use cases. None of this pattern is set in stone. It just gives you a common way to construct different services. @mattt also has some really cool patterns (Router and CRUD) in the Alamofire README which I would highly recommend reading through. They are definitely more complicated though and still require a Service
type of object to maximize code reuse.
Hopefully that helps shed some light.
Chain multiple Alamofire requests
Wrapping other asynchronous stuff in promises works like this:
func myThingy() -> Promise<AnyObject> {
return Promise{ fulfill, reject in
Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]).response { (_, _, data, error) in
if error == nil {
fulfill(data)
} else {
reject(error)
}
}
}
}
Edit: Nowadays, use: https://github.com/PromiseKit/Alamofire-
How is it possible to perform multiple Alamofire requests that are finished one after another?
The issue is just the same as in the related question you posed: the operation dependencies are on finishing an operation, as documented, but you have written code where the operation exits after asynchronously dispatching a request for future execution (the operations you created and added to a queue will finish in the order set by their dependencies, but the requests will be fired concurrently by the NSURLSession underlying Alamofire).
If you need serial execution, you can for instance do the following:
// you should create an operation queue, not use OperationQueue.main here –
// synchronous network IO that would end up waiting on main queue is a real bad idea.
let operationQueue = OperationQueue()
let timeout:TimeInterval = 30.0
for operationNumber in 0..<4 {
let operation = BlockOperation {
let s = DispatchSemaphore(value: 0)
self.performAlamofireRequest(operationNumber) { number in
// do stuff with the response.
s.signal()
}
// the timeout here is really an extra safety measure – the request itself should time out and end up firing the completion handler.
s.wait(timeout: DispatchTime(DispatchTime.now, Int64(timeout * Double(NSEC_PER_SEC))))
}
operationQueue.addOperation(operation)
}
Various other solutions are discussed in connection to this question, arguably a duplicate. There's also Alamofire-Synchronous.
Alamofire multiple requests iteration
If this is not running, you could be deadlocking if you use dispatch_group_wait
on the main thread, thereby blocking that thread, and preventing Alamofire from running any of its completion handlers (which also require the main thread). This is solved (assuming you are, indeed, using dispatch_group_wait
), by replacing that with dispatch_group_notify
.
Thus:
let group = dispatch_group_create()
for d in data {
// I enter the group
dispatch_group_enter(group)
dataService.getPoints(d.point)) { additionalPoints, error in
defer { dispatch_group_leave(group) }
guard let let additionalPoints = additionalPoints else {
print(error)
return
}
points.append(additionalPoints)
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
// go to the next iteration here
}
Where:
func getPoints(point: WhateverThisIs, completionHandler: (JSON?, NSError?) -> ()) {
let headers = [
"Authorization": "Bearer \(token)"
]
Alamofire.request(.GET, url, headers: headers)
.responseJSON { response in
switch response.result {
case .Success:
let json = JSON(data: response.data!)
completionHandler(json, nil)
case .Failure(let error):
completionHandler(nil, error)
}
}
}
Now, I don't know what your various parameter types were, so I was left to guess, so don't get lost in that. But the key is that (a) you should make sure that all paths within your Alamofire method will call the completion handler; and (b) the caller should use dispatch_group_notify
rather than dispatch_group_wait
, avoiding the blocking of any threads.
Note, in order to make the completion handler callable even if the network request failed, I had to make the parameter to that closure optional. And while I was there, I added an optional error parameter, too.
A few unrelated changes included in the above sample:
I'd suggest using a different variable name for the parameter of your closure. The
points.append(points)
in your original code snippet suggests some confusion between yourpoints
array and thepoints
that is passed back in the closure.You don't have to set the
Content-Type
header, as Alamofire does that for you.I didn't change it above, but it is inefficient to use
responseJSON
(which usesNSJSONSerialization
to parse the JSON) and then use SwiftyJSON to parse the rawdata
withNSJSONSerialization
again. Personally, I don't bother with SwiftyJSON, but if you want to use it, I'd suggest use Alamofire'sresponse
method ratherresponseJSON
. There's no point in parsing the JSON twice.
Alamofire nonblocking connection
You don't want to do the networking inside your model object. Instead, you want to handle the networking layer in some more abstract object such as a Service
of class methods. This is just a simple example, but I think this will really get you heading in a much better architectural direction.
import Alamofire
struct User {
let name: String
let companyName: String
}
class UserService {
typealias UserCompletionHandler = (User?, NSError?) -> Void
class func getUser(completionHandler: UserCompletionHandler) {
let loginRequest = Alamofire.request(.GET, "login/url")
loginRequest.responseJSON { request, response, json, error in
if let error = error {
completionHandler(nil, error)
} else {
println("Login Succeeded!")
let userRequest = Alamofire.request(.GET, "user/url")
userRequest.responseJSON { request, response, json, error in
if let error = error {
completionHandler(nil, error)
} else {
let jsonDictionary = json as [String: AnyObject]
let user = User(
name: jsonDictionary["name"]! as String,
companyName: jsonDictionary["companyName"]! as String
)
completionHandler(user, nil)
}
}
}
}
}
}
UserService.getUser { user, error in
if let user = user {
// do something awesome with my new user
println(user)
} else {
// figure out how to handle the error
println(error)
}
}
Since both the login and user requests are asynchronous, you cannot start using the User object until both requests are completed and you have a valid User object. Closures are a great way to capture logic to run after the completion of asynchronous tasks. Here are a couple other threads on Alamofire and async networking that may also help you out.
- Handling Multiple Network Calls
- Returning a Value with Alamofire
Hopefully this sheds some light.
Related Topics
Arkit - How to Put 3D Object on Qrcode
Create a Timer Publisher Using Swift Combine
Show/Hide Password - How to Add This Feature
Xcode Error: Missing Required Module 'Firebase'
Swift - Take Nil as Argument in Generic Function with Optional Argument
Swift Diffabledatasource Make Insert&Delete Instead of Reload
Xcode: Could Not Load Modelio.Framework, Scenekit.Framework, etc
Variable P Passed by Reference Before Being Initialized
How to Define an Enum as a Subset of Another Enum's Cases
How to Create a Hud on Top of My Scenekit.Scene
Using Key-Value Programming (Kvp) with Swift
iOS - Arkit Node Disappear After 100M
Easiest Way to Find Square Root in Swift
Get Path to Swift Script from Within Script