How do I make asynchronous callbacks in swift? I'm writing a little Framework for my app because it's supposed to run on both, iOS und OS X. So I put the main code that is not device-specific into this framework that also handles requests to my online api. And obviously I also want the app's GUI and therefore my ViewControllers to react as soon as a api request has finished. In Objective-C I've done this by saving the view containing the function that had to be called in an id variable and the function itself in a selector variable. Then I invoked the function using the following code:
SEL selector = callbackMethod;
((void (*)(id, SEL))[callbackViewController methodForSelector:selector])(callbackViewController, selector);
How can I accomplish this in swift? Or is there a better way of doing this?
I really appreciate all your help!
I've shared the pattern that I use for this scenario in the following gist: https://gist.github.com/szehnder/84b0bd6f45a7f3f99306
Basically, I create a singleton DataProvider.swift that setups an AFNetworking client. Then the View Controllers call methods on that DataProvider, each of which is terminated by a closure that I've defined as a typealias called ServiceResponse. This closure returns either a dictionary or an error.
It allows you to very cleanly (imo) call for an async data action from the VC's with a very clear indication of what you want performed when that async response returns.
DataProvider.swift
typealias ServiceResponse = (NSDictionary?, NSError?) -> Void
class DataProvider: NSObject {
var client:AFHTTPRequestOperationManager?
let LOGIN_URL = "/api/v1/login"
class var sharedInstance:DataProvider {
struct Singleton {
static let instance = DataProvider()
}
return Singleton.instance
}
func setupClientWithBaseURLString(urlString:String) {
client = AFHTTPRequestOperationManager(baseURL: NSURL.URLWithString(urlString))
client!.operationQueue = NSOperationQueue.mainQueue()
client!.responseSerializer = AFJSONResponseSerializer()
client!.requestSerializer = AFJSONRequestSerializer()
}
func loginWithEmailPassword(email:String, password:String, onCompletion: ServiceResponse) -> Void {
self.client!.POST(LOGIN_URL, parameters: ["email":email, "password":password] , success: {(operation:AFHTTPRequestOperation!, responseObject:AnyObject!) -> Void in
self.setupClientWithBaseURLString("http://somebaseurl.com")
let responseDict = responseObject as NSDictionary
// Note: This is where you would serialize the nsdictionary in the responseObject into one of your own model classes (or core data classes)
onCompletion(responseDict, nil)
}, failure: {(operation: AFHTTPRequestOperation!, error:NSError!) -> Void in
onCompletion(nil, error)
})
}
}
MyViewController.swift
import UIKit
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
DataProvider.sharedInstance.loginWithEmailPassword(email:"some#email.com", password:"somepassword") { (responseObject:NSDictionary?, error:NSError?) in
if (error) {
println("Error logging you in!")
} else {
println("Do something in the view controller in response to successful login!")
}
}
}
}
I'd like to recommend use a block or closure callback instead of using NSThread and selectors.
For example, in my API I have follow method:
Swift:
Below you will find an updated implementation.
func getUsers(completion: (result: NSArray?, error: NSError?)->())
{
var session = NSURLSession.sharedSession()
var task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if error != nil {
completion(nil, error)
} else {
var result:NSArray = data to NSArray;
completion(result, nil)
}
}
task.resume()
}
Objective-C:
...
typedef void (^CBSuccessBlock)(id result);
typedef void (^CBFailureBlock)(NSError *error);
...
- (void)usersWithSucces:(CBSuccessBlock)success failure:(CBFailureBlock)failure
{
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:url]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSArray *users = //convert data to array
if(error)
failure(error);
else
success(users);
}] resume];
}
Then, just make a call to api from view controller:
Objc:
[api usersWithSucces:^(id result)
{
//Success callback
} failure:^(NSError *error)
{
//Failure callback
}];
Swift:
api.getUsers({(result: AnyObject?, error: NSError?) -> Int in
// callback here
})
UPDATE:
Meanwhile, I see that the question and answers are still being useful and interested. Well, here is an updated version of swift implementation using generic enum as a result object:
//Generic enum that represents the result
enum AsyncResult<T>
{
case Success(T)
case Failure(NSError?)
}
class CustomUserObject
{
}
func getUsers(completion: (AsyncResult<[CustomUserObject]>)->())
{
let request = NSURLRequest()
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if let error = error
{
completion(AsyncResult.Failure(error))
} else {
let result: [CustomUserObject] = []//deserialization json data into array of [CustomUserObject]
completion(AsyncResult.Success(result))
}
}
task.resume()
}
//Usage:
getUsers { (result) in
switch result
{
case .Success(let users):
/* work with users*/
break
case .Failure(let error):
/* present an error */
break
}
}
I've just made this little example:
Swift: Async callback block pattern example
Basically there is ClassA:
//ClassA it's the owner of the callback, he will trigger the callback when it's the time
class ClassA {
//The property of that will be associated to the ClassB callback
var callbackBlock : ((error : NSError?, message : String?, adress : String? ) -> Void)?
init() {
//Do Your staff
}
//Define your function with the clousure as a parameter
func yourFunctionWithCallback(#functionCallbackParameter : (error : NSError?,message : String?, adress : String?) -> ()) {
//Set the calback with the calback in the function parameter
self.callbackBlock = functionCallbackParameter
}
//Later On..
func callbackTrigger() {
self.callbackBlock?(error: nil,message: "Hello callback", adress: "I don't know")
}
}
And ClassB:
//ClassB it's the callback reciver the callback
class ClassB {
#IBAction func testCallbackFunction(sender: UIButton) {
let classA = ClassA()
classA.yourFunctionWithCallback { (error, message, adress) -> () in
//Do your stuff
}
}
}
ClassA: it's the owns a property witch is the callbackBlock. ClassB will set this property by Call the yourFunctionWithCallback function. Later on then ClassA it's ready, will trigger the callback by calling the callBackBlock inside the callbackTrigger function.
ClassB: will call the ClassA method to set the callback block and wait until the block has been triggered.
Can NSThread help you? :
NSThread.detachNewThreadSelector(<#selector: Selector#>, toTarget: <#AnyObject?#>, withObject: <#AnyObject?#>)
Related
I'm using AWSAppSyncClient to upload files but I'm struggling to connect the upload progress hook with the view.
AWSAppSyncClient is a property of the the application delegate initialized with an S3ObjectManager. The object manager method upload has access to the upload progress via the AWSTransferUtilityUplaodExpression:
expression.progressBlock = {(task, progress) in
DispatchQueue.main.async(execute: {
// Can we update the controller's progress bar here?
print("Progress: \(Float(progress.fractionCompleted))")
})
}
My controller invokes the upload by calling perform:
var appSyncClient: AWSAppSyncClient? // retrieved from the app delegate singleton
appSyncClient?.perform(mutation: CreatePostMutation(input: input)) { (result, error) in ...
What I am struggling with: how do I provide the S3ObjectManager a reference to the controller? I thought of instantiating the AWSAppSyncClient in each controller, and maybe using some sort of delegate pattern?
It's probably overkill to instantiate a new client on each view controller. Setup & teardown take a bit of time & system resources to perform, and you'd probably prefer to keep those activities separate from the view controller in any case, just for separation of responsibilities.
There isn't really a good way of registering a per-object listener, since mutations are queued for eventual, asynchronous delivery. Your delegate idea seems like the best approach at this point.
NOTE: Code below is untested, and not thread-safe.
For example, you could declare a singleton delegate that manages watchers for individual views that need to report progress:
class AppSyncS3ObjectManagerProgressWatcher {
typealias ProgressSubscription = UUID
static let shared = AppSyncS3ObjectManagerProgressWatcher()
private var watchers = [UUID: AppSyncS3ObjectManagerProgressDelegate?]()
func add(_ watcher: AppSyncS3ObjectManagerProgressDelegate) -> ProgressSubscription {
let subscription = UUID()
weak var weakWatcher = watcher
watchers[subscription] = weakWatcher
return subscription
}
func remove(_ subscription: ProgressSubscription?) {
guard let subscription = subscription else {
return
}
watchers[subscription] = nil
}
}
extension AppSyncS3ObjectManagerProgressWatcher: AppSyncS3ObjectManagerProgressDelegate {
func progressReportingExpression(forDownloadingObject object: AWSS3ObjectProtocol) -> AWSS3TransferUtilityDownloadExpression {
let expression = AWSS3TransferUtilityDownloadExpression()
expression.progressBlock = { _, progress in
self.didReportProgress(forDownloadingObject: object, progress: progress)
}
return expression
}
func progressReportingExpression(forUploadingObject object: AWSS3ObjectProtocol & AWSS3InputObjectProtocol) -> AWSS3TransferUtilityUploadExpression {
let expression = AWSS3TransferUtilityUploadExpression()
expression.progressBlock = { _, progress in
self.didReportProgress(forUploadingObject: object, progress: progress)
}
return expression
}
func didReportProgress(forDownloadingObject object: AWSS3ObjectProtocol, progress: Progress) {
for watcher in watchers.values {
watcher?.didReportProgress(forDownloadingObject: object, progress: progress)
}
}
func didReportProgress(forUploadingObject object: AWSS3ObjectProtocol & AWSS3InputObjectProtocol, progress: Progress) {
for watcher in watchers.values {
watcher?.didReportProgress(forUploadingObject: object, progress: progress)
}
}
}
Wherever you conform S3TransferUtility to S3ObjectManager, you would do something like:
extension AWSS3TransferUtility: AWSS3ObjectManager {
public func download(s3Object: AWSS3ObjectProtocol, toURL: URL, completion: #escaping ((Bool, Error?) -> Void)) {
let completionBlock: AWSS3TransferUtilityDownloadCompletionHandlerBlock = { task, url, data, error -> Void in
if let _ = error {
completion(false, error)
} else {
completion(true, nil)
}
}
let progressReportingExpression = AppSyncS3ObjectManagerProgressWatcher
.shared
.progressReportingExpression(forDownloadingObject: s3Object)
let _ = self.download(
to: toURL,
bucket: s3Object.getBucketName(),
key: s3Object.getKeyName(),
expression: progressReportingExpression,
completionHandler: completionBlock)
}
public func upload(s3Object: AWSS3ObjectProtocol & AWSS3InputObjectProtocol, completion: #escaping ((_ success: Bool, _ error: Error?) -> Void)) {
let completionBlock : AWSS3TransferUtilityUploadCompletionHandlerBlock = { task, error -> Void in
if let _ = error {
completion(false, error)
} else {
completion(true, nil)
}
}
let progressReportingExpression = AppSyncS3ObjectManagerProgressWatcher
.shared
.progressReportingExpression(forUploadingObject: s3Object)
let _ = self.uploadFile(
s3Object.getLocalSourceFileURL()!,
bucket: s3Object.getBucketName(),
key: s3Object.getKeyName(),
contentType: s3Object.getMimeType(),
expression: progressReportingExpression,
completionHandler: completionBlock
).continueWith { (task) -> Any? in
if let err = task.error {
completion(false, err)
}
return nil
}
}
}
And then in the progress reporting view:
override func awakeFromNib() {
super.awakeFromNib()
progressSubscription = AppSyncS3ObjectManagerProgressWatcher.shared.add(self)
}
func didReportProgress(forUploadingObject object: AWSS3InputObjectProtocol & AWSS3ObjectProtocol, progress: Progress) {
// TODO: Filter by object local URI/key/etc to ensure we're updating the correct progress
print("Progress received for \(object.getKeyName()): \(progress.fractionCompleted)")
self.progress = progress
}
As I noted, this code is untested, but it should outline a general approach for you to start from. I'd welcome your feedback and would like to hear what approach you eventually settle on.
Finally, please feel free to open a feature request on our issues page: https://github.com/awslabs/aws-mobile-appsync-sdk-ios/issues
I want to run some tasks which are dependent on each other so should be performed in an order. Currently, it blocks my UI thread and also there is some issue in ordering.
Couple of questions regarding this:
Tasks are not performed in correct order. What change to be made if we want them to be performed one after other
Is the code optimised in terms of memory usage and resource consumption? How it can be made more optimised?
Do we need global queues inside function call also as shown in code below?
Here are my code details. I have created some serial queues as follows:
var Q0_sendDisplayName=dispatch_queue_create("Q0_sendDisplayName",DISPATCH_QUEUE_SERIAL)
var Q1_fetchFromDevice=dispatch_queue_create("fetchFromDevice",DISPATCH_QUEUE_SERIAL)
var Q2_sendPhonesToServer=dispatch_queue_create("sendPhonesToServer",DISPATCH_QUEUE_SERIAL)
I have an idea that serial queues perform tasks in order so i have called my tasks on serial queues. Here is my code:
dispatch_sync(Q0_sendDisplayName,
{
self.sendNameToServer(displayName){ (result) -> () in
dispatch_sync(self.Q1_fetchFromDevice,
{
self.SyncfetchContacts({ (result) -> () in
dispatch_sync(self.Q2_sendPhonesToServer,
{ self.SyncSendPhoneNumbersToServer(self.syncPhonesList, completion: { (result) in
//.......
//....
The code inside these functions is also running on global queue. Dont know if it is a correct way to code. I have used completion handlers to notify that method has completed executing. Here is the code of function1:
func sendNameToServer(var displayName:String,completion:(result:Bool)->())
{
Alamofire.request(.POST,"\(urlToSendDisplayName)",headers:header,parameters:["display_name":displayName]).responseJSON{
response in
switch response.result {
case .Success:
return completion(result: true) //......
Here is the code of function2. This function is long as it reads whole contacts book so i have placed it inside global queue(dont know if it is right way). I call completion handler on main queue. Here is code:
func SyncfetchContacts(completion:(result:Bool)->())
{
let contactStore = CNContactStore()
var keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactPhoneNumbersKey, CNContactImageDataAvailableKey,CNContactThumbnailImageDataKey, CNContactImageDataKey]
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)){
do {
try contactStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: keys)) { (contact, pointer) -> Void in
if (contact.isKeyAvailable(CNContactPhoneNumbersKey)) {
for phoneNumber:CNLabeledValue in contact.phoneNumbers {
let a = phoneNumber.value as! CNPhoneNumber
}
}
}
dispatch_async(dispatch_get_main_queue())
{
completion(result: true)
}
}
//........
Here is the code for function3 which again inside has a global queue(dont know if its right) and calls completion handler on main queue.
func SyncSendPhoneNumbersToServer(phones:[String],completion: (result:Bool)->()){
Alamofire.request(.POST,"\(url)",headers:header,parameters:["display_name":displayName]).responseJSON{
response in
switch response.result {
case .Success:
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0))
{
//enter some large data in database in a loop
dispatch_async(dispatch_get_main_queue())
{
return completion(result: true)
}
}//......
In SyncfetchContacts you are calling the completion handler before contactStore.enumerateContactsWithFetchRequest has finished, outside of its completion closure.
Simply move it in there:
func SyncfetchContacts(completion:(result:Bool)->()) {
...
do {
try contactStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: keys)) { (contact, pointer) -> Void in
if (contact.isKeyAvailable(CNContactPhoneNumbersKey)) {
for phoneNumber:CNLabeledValue in contact.phoneNumbers {
let a = phoneNumber.value as! CNPhoneNumber
}
}
// here ...
dispatch_async(dispatch_get_main_queue()) {
completion(result: true)
}
}
// ... not here.
// dispatch_async(dispatch_get_main_queue()) {
// completion(result: true)
// }
}
}
So, I created a typealias to store a completion handler, to later pass into a function called submitTokenToBackend using Stripe's iOS library. Here is my code:
// MARK: - Create Completion Handlers
typealias CompletionHandler = (_ token: AnyObject?, _ error: NSError?) -> Void
// MARK: - Submit Token To Backend
func submitTokenToBackend(completionHandler: CompletionHandler) {
}
// MARK: - STPPaymentCardTextFieldDelegate
func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) {
print("Card number: \(textField.cardParams.number) Exp Month: \(textField.cardParams.expMonth) Exp Year: \(textField.cardParams.expYear) CVC: \(textField.cardParams.cvc)")
self.buyButton.isEnabled = textField.isValid
}
// MARK: Initialize Card Params
let cardParams = STPCardParams()
func cardParamsFunc() {
cardParams.number = "4242424242424242"
cardParams.expMonth = 10
cardParams.expYear = 2018
cardParams.cvc = "123"
STPAPIClient.shared().createToken(withCard: cardParams){ (token, error) in
if let error = error {
print(error.localizedDescription)
} else if let token = token {
// HERE'S WHERE I'M GETTING ERRORS
self.submitTokenToBackend(completionHandler: CompletionHandler) -> Void {
if let error = error {
print(error.localizedDescription)
} else {
print("Show receipt page")
}
}
}
}
}
I am getting these weird errors, now, in Swift 3 concerning my completion handler not having expected types. Not an isolated incident, either. Any thoughts?
Almost all thing you need is described in Rob Napier's answer.
I'll try to show you a little more concrete code...
You can define the completion handler and pass it to submitTokenToBackend(completionHandler:) like this:
let theCompletionHandler: CompletionHandler = {token, error in
if let error = error {
print(error.localizedDescription)
} else {
print("Show receipt page")
}
}
self.submitTokenToBackend(completionHandler: theCompletionHandler)
With removing intermediate let-constant, you can write it in this way:
self.submitTokenToBackend(completionHandler: {token, error in
if let error = error {
print(error.localizedDescription)
} else {
print("Show receipt page")
}
})
Using the trailing closure feature of Swift, the above code can be shortened to:
self.submitTokenToBackend {token, error in
if let error = error {
print(error.localizedDescription)
} else {
print("Show receipt page")
}
}
Your code is far from any of above three.
self.submitTokenToBackend(completionHandler: CompletionHandler) -> Void {
This is a declaration, not a method call. You can't pass a typealias as a parameter. And -> Void does not make sense here at all. You almost certainly meant
self.submitTokenToBackend {
If you have further questions on this, however, you need to provide code we can compile (see mcve) and list the exact errors. "Weird errors" is not particularly helpful for debugging.
Im not sure, but... Where is 'CompletionHandler' implementation? As I understand, you just declarate some like block in Objective - C ('typedef void (^completionHandler)(id token);'), but don't use it.
I am writing a Swift function using closure. A should-be-compilable code sample is like this,
import Foundation
typealias PKSynchronizeProgressBlock = (Double) -> Void
typealias PKSynchronizeCompletionBlock = (Bool, NSError?) -> Void
class X {
func synchronizeAppDataWithProgress(
progress: PKSynchronizeProgressBlock?,
completion: PKSynchronizeCompletionBlock?) {
dispatch_async(dispatch_get_main_queue(), {
// Do a lot of downloading, and during the process
// {
// If progress is updated
if (progress != nil) {
progress!(Double(0))
}
//
// If something goes wrong
if (completion != nil) {
completion!(false, nil)
}
// }
dispatch_async(dispatch_get_main_queue(), {
if (completion != nil) {
completion!(true, nil)
}
})
})
}
func foo() {
self.synchronizeAppDataWithProgress({ (progress: Double) -> Void in
self.launchProgressBar.progress = progress
}, completion: { (success: Bool, error: NSError?) -> Void in
if success {
self.launchProgressBar.progress = 1.0
}
else {
print("Failed to synchronize app data with error %#", error!)
}
})
}
}
However, this code does not compile. Xcode says that
cannot invoke 'synchronizeAppDataWithProgress' with an argument list
'(progress: (Double) -> Void, completion: (Bool, NSError?) -> Void)'
What should I do? Did I make any stupid mistake in my code?
Update:
Thanks to #Mario Zannone. I fixed the first two mistakes in my code above. That was: (1) I inserted a redundant progress: in the function call. I have removed that. (2) I updated UI in a thread other than main thread.
But the code still does not work if I don't comment out the following single line in the foo(),
self.launchProgressBar.progress = progress
Do you have any clue why?
Xcode can be picky sometimes with the way arguments are listed inside closures. I've found it best to leave the type inferred. Also be sure to use capture lists to avoid strong reference cycles in your closures.
Using the Alamofire dependency, I've rewritten your code above and it compiles.
import Alamofire
typealias ProgressBlock = (Double) -> Void
typealias CompletionBlock = (Bool, ErrorType?) -> Void
class ExampleDataSource {
func fetchData(progress: ProgressBlock?, completion: CompletionBlock?) {
// Here we use the Alamofire Dependency for progress reporting simplicity.
Alamofire.request(.GET, "https://www.myexampledomain.com")
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
// bytesRead, totalBytesRead, and totalBytesExpectedToRead are Int64
// so we must perform unit conversion
let progressPercentage = Double(totalBytesRead) / Double(totalBytesExpectedToRead)
// here we optionally call the ProgressBlock 'progress' and pass it the progressPercentage
progress?(progressPercentage)
}
.response { request, response, data, error in
// here we usually parse the data, but for simplicity we'll
// simply check to see if it exists.
let completionFlag = (data != nil)
// note that NSError? is interchangable with ErrorType?
completion?(completionFlag, error)
}
}
func performTasks() {
// first we'll set up our closures...
let progressBlock: ProgressBlock = { progress in
// here we update the UI or whatever
// the nice thing about the Alamofire dependency is
// that it's thread-safe :]
}
let completionBlock: CompletionBlock = { success, error in
// here we do whatever we need to do when the
// network operation finishes, or handle the
// errors appropriately
}
// then we'll pass them into our fetchData method
fetchData(progressBlock, completion: completionBlock)
}
}
Recently I read the source code of Alamofire, and I am really confused about How could Alamofire guarantee the response method would be called in correctly order. I hope someone(maybe matt lol) could help me out.
Example, an easy GET Request like this
Alamofire.request(.GET, "https://api.github.com/users/octocat/received_events")
After I analysed the work flow of it, I posted my understanding
Create the request and underlying NSURLSession.
public func request(method: Method, URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL) -> Request {
return Manager.sharedInstance.request(method, URLString, parameters: parameters, encoding: encoding)
}
This method would create a request: Request object, which would contain the underlying NSURLSessionDataTask object. Manager.sharedInstance has already set up a NSURLSession and set itself as that session's delegate. The Manager.sharedInstance object would save a customized object request.delegate in its own property delegate
After those objects were created, Alamofire would send this request immediately.
public func request(URLRequest: URLRequestConvertible) -> Request {
var dataTask: NSURLSessionDataTask?
dispatch_sync(queue) {
dataTask = self.session.dataTaskWithRequest(URLRequest.URLRequest)
}
let request = Request(session: session, task: dataTask!)
delegate[request.delegate.task] = request.delegate
if startRequestsImmediately {
request.resume()
}
return request
}
Since Manager.sharedInstance set itself as the underlying NSURLSession's delegate, when data received, the delegate methods would be called
public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
if dataTaskDidReceiveData != nil {
dataTaskDidReceiveData!(session, dataTask, data)
} else if let delegate = self[dataTask] as? Request.DataTaskDelegate {
delegate.URLSession(session, dataTask: dataTask, didReceiveData: data)
}
}
If a user want to get the response and do something related, he would use following public API
// Here the request is a Request object
self.request?.responseString { (request, response, body, error) in
// Something do with the response
}
let's see what the responseString(_: completionHandler:) method does
public func response(queue: dispatch_queue_t? = nil, serializer: Serializer, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
delegate.queue.addOperationWithBlock {
let (responseObject: AnyObject?, serializationError: NSError?) = serializer(self.request, self.response, self.delegate.data)
dispatch_async(queue ?? dispatch_get_main_queue()) {
completionHandler(self.request, self.response, responseObject, self.delegate.error ?? serializationError)
}
}
return self
}
My question is how 5 could be guaranteed to happen after 3, so the user could get all the response not part of it, because self.response at this time would be fully loaded.
Is it because of NSURLSession's background processing occurs on the same queue -- delegate.queue, which is created like this in Alamofire:
//class Request.TaskDelegate: NSObject, NSURLSessionTaskDelegate
self.queue = {
let operationQueue = NSOperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.suspended = true
if operationQueue.respondsToSelector("qualityOfService") {
operationQueue.qualityOfService = NSQualityOfService.Utility
}
return operationQueue
}()
Is my understanding correct, how does that happen? It might be require some understanding about RunLoop and NSURLSession's thread mechanism, if you could point out where I could refer to, thank you as well.