I'm using Alamofire (AF) in a concurrent operation queue to run network commands in my project. Sometimes AF's completionHandler doesn't fire, leaving my NSOperation hanging (waiting for a finish message that it will never receive).
Eg. I'll see the "response" log, but no corresponding "see me" log from AF's dispatch_async below:
public func response(priority: Int = DISPATCH_QUEUE_PRIORITY_DEFAULT, queue: dispatch_queue_t? = nil, serializer: (NSURLRequest, NSHTTPURLResponse?, NSData?, NSError?) -> (AnyObject?, NSError?), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
NSLog("markse-response")
dispatch_async(self.delegate.queue, {
NSLog("markse-see me")
dispatch_async(dispatch_get_global_queue(priority, 0), {
if var error = self.delegate.error {
dispatch_async(queue ?? dispatch_get_main_queue(), {
completionHandler(self.request, self.response, nil, error)
})
} else {
let (responseObject: AnyObject?, serializationError: NSError?) = serializer(self.request, self.response, self.delegate.data, nil)
dispatch_async(queue ?? dispatch_get_main_queue(), {
completionHandler(self.request, self.response, responseObject, serializationError)
})
}
})
})
return self
}
This is my NSOperation (AsynchronousCommand is an NSOperation subclass):
import Alamofire
class SettingsListCommand: AsynchronousCommand {
override func execute() {
if cancelled { return }
let endpoint = "https://api.github.com/users/cog404/orgs"
DLogVerbose("AF request")
weak var weakSelf = self
Alamofire.request(.GET,
endpoint,
parameters:nil)
.responseJSON {(request, response, JSON, error) in
DLogVerbose("AF response")
if let strongSelf = weakSelf {
if strongSelf.cancelled {
strongSelf.finish()
return
}
DLogVerbose(JSON)
strongSelf.finish()
}
}
}
}
This only happens 'occasionally', making this very difficult to debug.
Does anyone with a good understanding of threading know what could be going wrong?
Any advice very much appreciated. A project to help illustrate the problem is here.
The request delegate's dispatch queue is serial, meaning that it will only process a single block at a time, in order of when the blocks were dispatched (FIFO). If the second log statement isn't firing, it's because the previous block didn't yet finish.
Related
I perform the next code
let task = session.uploadTask(with: request, from: requestData.body) { data, response, error in
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
All is clear, after doing network request in background I return completion to .main . But how to handle case, if I want to call completion not in .main but in thread in which session.uploadTask was initiated, because in my application it could be not only .main .
There is not a GCD mechanism to retrieve the current dispatch queue so that you can later dispatch to it. (A long time ago, there used to be a way to fetch the current queue, but it was deprecated back in iOS 7, and even then it was “Recommended for debugging and logging purposes only.”)
If you want to call the completion handlers on specific dispatch queue, I would suggest supplying an explicit DispatchQueue parameter to the method. Below, I have it default to .main, but the caller can override that with whatever it wants:
func perform(_ request: URLRequest, with data: Data, on queue: DispatchQueue = .main, completion: #escaping (Result<T, Error>) -> Void) {
let task = session.uploadTask(with: request, from: data) { data, response, error in
if let error = error {
queue.async {
completion(.failure(error))
}
}
...
}
...
}
I know that this is not precisely what you are looking for, but it is an easy way to let the caller specify on which queue your closure will be called.
If you are using operation queues, you can get the current to determine the current operation queue. And should one do this, one would use addOperation on that operation queue in order to call the completion handler.
func perform(_ request: URLRequest, with data: Data, completion: #escaping (Result<T, Error>) -> Void) {
guard let queue = OperationQueue.current else {
fatalError("Must be called from operation queue")
}
let task = session.uploadTask(with: request, from: data) { data, response, error in
if let error = error {
queue.addOperation {
completion(.failure(error))
}
}
...
}
...
}
But this pattern only works if the caller was using operation queue, not when only using dispatch queues. For this reason, I would still be inclined to adopt the pattern of supplying the target operation queue as a parameter:
func perform(_ request: URLRequest, with data: Data, on queue: OperationQueue = .main, completion: #escaping (Result<T, Error>) -> Void) {
let task = session.uploadTask(with: request, from: data) { data, response, error in
if let error = error {
queue.addOperation {
completion(.failure(error))
}
}
...
}
...
}
If you would like to call completion on the current thread simply call completion().
current thread
completion(.failure(error))
main thread
DispatchQueue.main.async {
completion(.failure(error))
}
background thread
Dispatch.global(qos: .background) {
completion(.failure(error))
}
I used this solution:
guard let currentDispatch = OperationQueue.current?.underlyingQueue else {
return
}
let task = session.uploadTask(with: request, from: requestData.body) { data, response, error in
if let error = error {
currentDispatch.async {
completion(.failure(error))
}
}
}
Have not tested it too much, but quick debugging shows what I wanted to achieve,
I'm trying to write failing tests for my network calls but can't find anyway to access the connection settings from within my tests.
This code works fine for testing a success case:
func testRetrieveProducts() {
let expectation = expectationWithDescription("asynchronous request")
Requests().retrieveProducts({ (products) -> () in
// check that we have two arrays returned.
XCTAssert(products.count == 2)
expectation.fulfill()
}) { (error) -> () in
XCTFail("Request failed")
}
waitForExpectationsWithTimeout(5.0, handler: nil)
}
But I've been looking for a way to test network timeouts, and incorrect data being returned.
I can probably test incorrect data by calling functions individually inside the retrieveProducts function and stubbing stuff out, but doing something as simple as turning off the internet is proving to be quite difficult.
I know we have access to the Network Link Conditioner, but turning this on and off for each test isn't an option.
I'd love to have access to something as simple as:
func testFailRetrieveProducts() {
let expectation = expectationWithDescription("asynchronous request")
SomeNetworkClass.disableInternet()
Requests().retrieveProducts({ (products) -> () in
}) { (error) -> () in
XCTAssert(error == SomeError.TimeoutError)
}
waitForExpectationsWithTimeout(5.0, handler: nil)
}
Any solutions out there that can handle what I'm after, or am I going about this all wrong?
Take a look at this NSHipster article about Apple's Network Link Conditioner. There's a lot of presets and you can create your own custom network profile. Unfortunately there doesn't seem to be a way to throttle the network in code.
A somewhat viable alternative however is to use ReactiveCocoa and model all your network events as SignalProducers. Then you can use the throttle or wait function, depending on your intentions.
I ended up just mocking the network calls, which to be honest is a lot better than performing tests over an actual connection, as these can be very unreliable anyway.
Here's my mock NSURLSession
class MockSession: NSURLSession {
var completionHandler:((NSData!, NSURLResponse!, NSError!) -> Void)?
static var mockResponse: (data: NSData?, urlResponse: NSURLResponse?, error: NSError?)
override class func sharedSession() -> NSURLSession {
return MockSession()
}
override func dataTaskWithRequest(request: NSURLRequest, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDataTask {
self.completionHandler = completionHandler
return MockTask(response: MockSession.mockResponse, completionHandler: completionHandler)
}
class MockTask: NSURLSessionDataTask {
typealias Response = (data: NSData?, urlResponse: NSURLResponse?, error: NSError?)
var mockResponse: Response
let completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?
init(response: Response, completionHandler:((NSData!, NSURLResponse!, NSError!) -> Void)?) {
self.mockResponse = response
self.completionHandler = completionHandler
}
override func resume() {
completionHandler!(mockResponse.data, mockResponse.urlResponse, mockResponse.error)
}
}
}
Here's how I use it in a test:
Note that you still have to use an expectation even though there's no network delay.
func testRetrieveProductsValidResponse() {
let testBundle = NSBundle(forClass: self.dynamicType)
let filepath = testBundle.pathForResource("products", ofType: "txt")
let data = NSData(contentsOfFile: filepath!)
let urlResponse = NSHTTPURLResponse(URL: NSURL(string: "https://anyURL.com")!, statusCode: 200, HTTPVersion: nil, headerFields: nil)
MockSession.mockResponse = (data, urlResponse: urlResponse, error: nil)
let requestsClass = RequestManager()
requestsClass.Session = MockSession.self
let expectation = expectationWithDescription("ready")
requestsClass.retrieveProducts("N/R FOR TEST", branchID: "N/R FOR TEST", products: { (products) -> () in
XCTAssertTrue(products.count == 7)
expectation.fulfill()
}) { (error) -> () in
XCTAssertFalse(error == Errors.NetworkError, "Its a network error")
XCTAssertFalse(error == Errors.ParseError, "Its a parse error")
XCTFail("Error not covered by previous asserts. Shouln't get to here anyway.")
expectation.fulfill()
}
waitForExpectationsWithTimeout(3.0, handler: nil)
}
Finally, I have an accessible property on my RequestManager class that I can swap out with the MockSession when doing my tests.
var Session = NSURLSession.self
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)
}
}
I am writing a FileProvider extension (NSFileProviderExtension) for iOS. When "itemChangedAtURL" is called, I want to take the data from the NSURL and commit it back to my server. The problem I am having is that I will use Alamofire to make a request call and the request will never fire. I have tried setting backgroundSessionConfigurationWithIdentifier and using a sharedContainerIdentifier but none of that worked either. The same alamofire calls worked outside of the FileProvider extension. Does anyone know how exactly I have to setup NSURLSession or Alamofire to get this to work correctly?
I ran into this similar problem with AFNetworking. I decided to wrap my calls in a block based on what I read in http://z43studio.com/2015/04/storage-providers/
override func itemChangedAtURL(url: NSURL) {
var manager = AFHTTPSessionManager()
var request = manager.requestSerializer.multipartFormRequestWithMethod("PUT", URLString: "http://somewhere.awesome", parameters: nil, constructingBodyWithBlock: { (data: AFMultipartFormData!) -> Void in
var res = data.appendPartWithFileURL(fileUrl, name: "custom_name", error: nil)
}, error: nil)
var semaphore = dispatch_semaphore_create(0)
let task:NSURLSessionDataTask = manager.uploadTaskWithStreamedRequest(request, progress: nil,
completionHandler: {(response: NSURLResponse!, responseObject: AnyObject!, error: NSError!) -> Void in
if ((error) != nil) {
println("ERROR")
// Attempt to recover - otherwise release
dispatch_semaphore_signal(semaphore)
} else {
println("SUCCESS")
dispatch_semaphore_signal(semaphore)
}
})
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
func authenticate(completion:(success: Bool) -> Void) {
let qos = Int(QOS_CLASS_USER_INITIATED.value)
dispatch_async(dispatch_get_global_queue(qos, 0)){ () -> Void in
Alamofire.request(.POST, CONSTANTS.Domain+"/accounts", parameters: ["" : ""]).responseJSON { (req, res, json, error) in
dispatch_async(dispatch_get_main_queue()){
completion(success: true)
}
}
}
}
Or, can I leave out the dispatch and just keep my code simple?
Alamofire is designed to be asynchronous. On another note, if the method has as callback, most likely it is asynchronous.
So, yes you can leave out the dispatch_async calls.