Setting multiple key/value pairs for NSMutabledictionary in NSURLSession repsonse block - ios

I'm using NSURLSession to make multiple asynchronous requests to my server with following code:
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
...
[self.dict setObject:some_obj forKey:some_key];
}] resume];
Inside the response block I'm setting key/value pairs for an mutable Dictionary.
My question is:
As the requests are asynchronous, can it be, that theoretically my program tries to set key/value pairs for the dictionary at the same time? An if this is possible, what will happen?
Does the app crash?
Will be certain key/value pairs not set?
Or will it work, as one key/value setting will wait for the other to finish?
If 3. is not the case, what can I do to make 3) work?

NSMutableDictionary is not documented as thread-safe, so it almost certainly isn't.
However, the Apple docs on NSURLSession say:
The completion handler to call when the load request is complete. This handler is executed on the delegate queue.
You (may) pass the delegate queue at session creation, the docs say:
An operation queue for scheduling the delegate calls and completion handlers. The queue need not be a serial queue. If nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.
So as far as setting the keys, if you don't explicitly create the session with a parallel queue you should be fine. If you did, then you'll need to synchronize access. The easiest way is an #synchronized block:
#synchronized (self.dict) {
self.dict[key] = value;
}
Depending on when and where you're reading the values, you may need the synchronized block anyway.

Related

NSURLSession: method dataTaskWithRequest never reach completion callback on lengthy responses

The following code that used to create communication session with a remote server and send/receive HTTP requests/responses.
However, when a large file is attached to the response, the callback block never reached.
Only when explicitly invoke the cancel method after some timeout from the NSURLSession task (_dataTask), this callback is being called.
notice that using tcpdump it can easily observed that the response was properly received on the client side.
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
NSURLSession* session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:queue];
_dataTask = [session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if ([error code] == NSURLErrorCancelled) {
writeLog(LOG_ERROR, "NSURLErrorCancelled");
} else {
...
}
}];
[_dataTask resume]
// after timeout, the operation is cancelled.
sleep(100)
[_dataTask cancel];
I'd like to know if using dataTask has response length limit (because it's working for small files on response body) and if there is such a limit, so which other method should I use in order to overcome it.
I saw that there's an alternative method in NSUrlsession dedicated for downloading files called downloadTaskWithRequest but it doesn't have an async completion block.
Thanks !
When fetching large resources, you should use download task. A data task will attempt to load the entire response in a single NSData object. Loading a large asset in memory at the same time is not only inefficient, but if it is extraordinarily large, can cause problems.
A download task is well suited for these tasks, because it will stream the asset to a temporary file for you, reducing the peak memory usage. (Admittedly, you can manually achieve the same with data task with delegate pattern, but download tasks do this for you.)
You said:
I saw that there's an alternative method in NSURLSession dedicated for downloading files called downloadTaskWithRequest but it doesn't have an async completion block.
Two observations:
There is a rendition, dataTaskWithRequest:completionHandler:, that has a completion block:
NSURLSession* session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
...
}];
[task resume];
Note, I would advise using sharedSession if you are not setting a delegate, or otherwise customizing your NSURLSession. You do not want to instantiate NSURLSession objects unnecessarily. And if you really must instantiate a NSURLSession, re-use it for subsequent tasks and/or make sure to call finishTasksAndInvalidate after submitting the last task for that session, or else the NSURLSession will leak. And, if you instantiate your own NSURLSession, you do not have to instantiate your own operation queue, as it will, by default, create a serial queue for you if you do not supply an operation queue.
The rendition without a block parameter, downloadTaskWithURL:, works, too. All you need to do is to specify a delegate for your NSURLSession and then and implement URLSession:downloadTask:didFinishDownloadingToURL:.
The reason I suggest this is that, often, when we are downloading very large assets (especially over cellular), we realize that the users may want to leave our app and let the download complete in the background. In those situations, we would use a background NSURLSessionConfiguration. And when using background sessions, you must use this delegate-based approach. So, if you think you might eventually adopt background sessions for long downloads, then adopting a delegate-based approach now is not a bad idea.
For more information, see Downloading Files in the Background.

URLSession completion block not executing on delegate queue shown by Xcode

I have configured a URLSession to fetch data from network & have set a delegate queue so that all subsequent operations happen within my queue.
However, when using breakpoints to view the Debug navigator, Xcode shows the completion block of URLSession is invoked on arbitrary threads.
The URLSession is setup as follows -
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.urlSession = [NSURLSession sessionWithConfiguration:configuration delegate:nil delegateQueue:[MyManager sharedInstance].queue];
...
[self.urlSession dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
/... operations expected to be executed on WEGQueue .../
}] resume];
Below is screen capture of processes on my serial queue named WEGQueue before URLSession has started.
Now I expect the completion block operations to be invoked on the specified delegate queue of URLSession i.e. WEGQueue here.
However, using a breakpoint to view the Debug Navigator shows that block is being processed on an arbitrary queue. Attached pic below.
Below is Debug Navigator in "View Process by Queue" filter.
This is really weird!
I'm not sure why URLSession is not invoking completion blocks on the specified delegate queue.
And that's not all.
It gets weirder due to the fact that when I do a po, lldb says that the completion block is (as expected) on the WEGQueue. See pic below.
It's confusing that Xcode's Debug Navigator says URLSession's completion block is being executed on an arbitrary thread while lldb says it is executed as expected on the delegate queue WEGQueue.
Did anyone face a similar scenario? Is this just an Xcode GUI bug? Or something is really amiss over here?
I'd say it is just an XCode GUI limitation, that it does not always label the Queue in the "view by queue" with the name that you gave it. And it does not always label the threads with the Queue that thread is currently handling. As you say, when you do po [NSOperationQueue currentQueue], it does report the correct queue. So you have no indication that it actually uses another queue.
There is no guarantee that the same queue is always handled by the same thread. Each event can be handled by a different thread. There is only a guarantee that the events are not running in parallel, for a Serial Queue.

Are NSURLSessionDataTask completion blocks called on the main thread?

I have been trying to figure out when it's okay to "just type in what I need done" and when I need to be specific about what kind of work I am doing on what kind of thread.
As I understand I should only update the UI on my main thread. Does this mean that it's not okay to do something like this? Should I put this into a GDC call?
[sessionManager dataTaskWithRequest:aRequest completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
someUILabel.text = #"Hello!"; // Updating my UI
[someTableView reloadData]; // Ask a table view to reload data
}];
That's it for the UI part. Now, let's assume I had an NSMutableArray somewhere in in my class. I would be adding or removing objects to this array by for instance tapping a UIButton. Then again I have a NSURLSessionDataTask going to a server somewhere to get some data and load it into my NSMutableArray, like so:
[sessionManager dataTaskWithRequest:aRequest completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
myMutableArray = [[responseObject objectForKey:#"results"] mutableCopy];
}];
This is not a UI operation. Does this need to be wrapped in a GDC call to avoid crashing in a race condition between my button-tap adding an object (i.e. [myMutableArray insertObject:someObj atIndex:4];) while the completion block runs, or are these designed to not clash into each other?
I have left out all error handling to focus on the question at hand.
TLDR: It costs you nothing to call dispatch_async(dispatch_get_main_queue()... inside your completion handler, so just do it.
Long Answer:
Let's look at the documentation, shall we?
completionHandler The completion handler to call when the load request is complete. This handler is executed on the delegate queue.
The delegate queue is the queue you passed in when you created the NSURLSession with sessionWithConfiguration:delegate:delegateQueue:. If that's not how you created this NSURLSession, then I suggest you make no assumptions about what queue the completion handler is called on. If you didn't pass [NSOperationQueue mainQueue] as this parameter, you are on a background queue and you should break out to the main queue before doing anything that is not thread-safe.
So now the question is:
Is it thread-safe to update the UI and talk to the table view? No, you must do those things only on the main queue.
Is it thread-safe to set myMutableArray? No, because you would then be sharing a property, self.myMutableArray, between two threads (the main queue, where you usually talk to this property, and this queue, whatever it is).

Is a __block variable assignment thread-safe to read immediately after the block?

__block NSHTTPURLResponse *httpResponse;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error)
httpResponse = (NSHTTPURLResponse *)response;
}
dispatch_semaphore_signal(semaphore);
}];
[task resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
Is it safe to read httpResponse after this? The semaphore waits for the block to compete execution. If there was no error, will the assignment be seen immediately or do I have to synchronise or create a memory barrier outside the block?
Does waiting on the semaphore implicitly perform some synchronisation which makes the __block variable safe to read immediately. If this was done with Thread.join() in Java instead of a semaphore, it would be safe since it guarantees a happens-before relationship with the assignment in the "block".
The short answer is yes.
The semaphore lock essentially forces the thread that is currently operating to stop execution until it receives enough unlock signals to proceed.
The variable you have defined is modified on some other thread before the semaphore is allowed to continue executing, so your assignment should have safely occurred.
Strictly speaking, this code will block on the executing thread (probably the main thread) until the semaphore lock is signalled. So - short answer, yes it should work, but it isn't best practice because it blocks the main thread.
Longer answer:
Yes, the semaphore will make sure the __block captured storage isn't accessed until it has been filled in. However, the calling thread will be blocked by the wait until the block has completed. This isn't ideal - normal UI tasks like making sure Activity Indicators spin won't happen.
Best practice would be to have the block signal the main object (potentially using a dispatch_async call to the main queue) once it has completed, and only accessing it after that. This is especially true given that if your session task fails (e.g. from network connectivity), then the calling thread will potentially block until the completion handler is called with a timeout error. This will appear to a user like the app has frozen, and they can't really do anything about it but kill the app.
For more information on working with blocks, see:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
For best practice with Data Session tasks in particular:
http://www.raywenderlich.com/51127/nsurlsession-tutorial
It seems dispatch_semaphore_wait is also a memory barrier, so the value can be safely read.

Recursive / Iterative NSURLSessionDataTask causing memory leak

I am having a problem with memory leaking in my code, I have a need to GET many URL's in quick succession, each GET is influenced by the result of the previous GET. The purpose is to look for a specific piece of content within the response.
I found the cleanest way to implement this is recursively, as I can use the same method to identify if the desired value is present in the response. Functionally it works very well, but it leaks memory as described below. I have also implemented the same functionality in an iterative fashion, and this also leaks memory.
To my mind it seems that the NSURLSession API is responsible for leaking this memory, and it only occurs when multiple calls are made in very quick succession. However, I would appreciate if anyone can point out any obvious mistakes I am making.
Update 10/09/14:
Updated to add a recursion counter, demonstrating the leak still occurs even if the code isn't executed an infinite number of times. Also tidied up the implementation slightly, re-using the NSURLSession and NSURLSessionConfiguration as properties within the view controller.
Sample Code:
- (void)performURLCallRecursive {
recursionLimiter++;
if (recursionLimiter > 10) {
[self.session finishTasksAndInvalidate];
return;
}
NSURL * checkURL = [NSURL URLWithString:#"http://www.google.com"];
__block NSMutableURLRequest * urlRequest = [[NSMutableURLRequest alloc] initWithURL:checkURL
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:0.0f];
__weak typeof(self) weakSelf = self;
NSURLSessionDataTask * task = [self.session dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError
*error) {
NSString * body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Body: %#", body);
[weakSelf performURLCallRecursive];
}];
[task resume];
}
#pragma mark - Getters
- (NSURLSessionConfiguration *)sessionConfiguration {
if (!_sessionConfiguration) {
_sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
[_sessionConfiguration setAllowsCellularAccess:NO];
[_sessionConfiguration setTimeoutIntervalForRequest:10.0f];
[_sessionConfiguration setTimeoutIntervalForResource:10.0f];
[_sessionConfiguration setURLCache:[[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil]];
}
return _sessionConfiguration;
}
- (NSURLSession *)session {
if (_session == nil) {
_session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration
delegate:[SPRSessionDelegate new]
delegateQueue:nil];
}
return _session;
}
The memory leaks as reported by instruments. (NB: These vary slightly every time, but for the most part contain the same leaks, just more or less of the same leaks):
Further Update:
So, I actually implemented the same code iteratively, and the memory leak still occurs. For this example I included a loop limiter so it doesn't execute for ever. Can anyone help me figure out what on earth is going on here?
- (void)performURLCallIterative
{
int loopLimiter = 0;
do {
NSURLSessionConfiguration * defaultSession = [NSURLSessionConfiguration defaultSessionConfiguration];
[defaultSession setAllowsCellularAccess:NO];
[defaultSession setTimeoutIntervalForRequest:10.0f];
[defaultSession setTimeoutIntervalForResource:10.0f];
NSURLSession * session = [NSURLSession sessionWithConfiguration:defaultSession
delegate:self
delegateQueue:nil];
NSURL * checkURL = [NSURL URLWithString:#"http://google.com"];
NSMutableURLRequest * urlRequest = [[NSMutableURLRequest alloc] initWithURL:checkURL
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:0.0f];
__weak NSURLSession * weakSession = session;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSessionDataTask * task = [session dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString * body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Body: %#", body);
dispatch_semaphore_signal(semaphore);
[weakSession invalidateAndCancel];
}];
[task resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
loopLimiter++;
} while (loopLimiter <= 6);
}
Update 10/09/14:
This is still occurring on iOS 8 for any Googlers who may have found their way here. As far as I am concerned this is a bug in iOS.
- Update 9/12/2014
Solution: wait for iOS8.
- Update 9/10/2014
Whoa, this is spiraling into some Nth dimension of complexity :P. I hope one way or another you get a break here quick.
I have a few other things for you to try.
1) Could you make sure NSZombies is turned off. In Xcode, Product->Scheme->Edit Scheme...->Enable Zombie Objects (NOT ticked).
2) Also try cachePolicy:NSURLCacheStorageNotAllowed for your NSMutableURLRequest.
3) Could you see if you are completing with an error? Just put this around your body string assignment...
if (error == nil)
{
//Enter data->string code here
}
4) Could you see if you are not getting status 200?
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
5) It is hard to picture exactly how your project is set up. I would have an NSObject type class that houses the NSURLSession methods, which is separate from the UIViewController class from which it is being called. The timer or whatever recursion method you wish to choose would then call the url session associated methods from the UIViewController.
- Update 9/9/2014
You are correct about my question (2). The data task is resumed before completion and after the data task completes the session is invalidated. I haven't seen it done this way, but it makes sense. Just tested on my end, no leaks with regards to [session invalidateAndCancel]...
Could you check that your completion handler executes? Perhaps it doesn't and the session is never cancelled before a new task is started?
I am noticing that there are a few references to HTTP Headers in the Instruments Leaks report, maybe if you are not specifying either a [urlRequest setHTTPMethod:#"GET"] the request is missing some basic headers?
(I'll edit after we find the solution, so this doesn't look like a discussion).
- Original 9/8/2014
Interesting question! I have troubleshot leaks associated with NSURLSessions. Definitely #autoreleasepool{} and others are good suggestions to try so far... But!
I am afraid the thing you asked us to look past might be the culprit here.
Just a few observations first:
1) It is not clear to me why you would need to __weak the self here. What is the retain cycle you are trying to avoid? Perhaps this is more clear in the code you are actually using aside from your "sample".
2) What is the reason for the call to invalidate the session before the data task associated with that session even has a chance to complete, let alone resume. The data task is in the suspended state until resumed.
3) If you are recursively running a method like this, then I think it is crucial to specify or at least consider what delegate queue, otherwise having it set to nil defaults it to serial operation queue. What happens when the delegate calls before the completion handler finishes, in an infinite loop - most likely a huge pile up.
--
I believe that the main issue here is that you are starting a new or canceling the NSURLSessionDataTask before it has a chance to complete. Look at +sesssionWithConfiguration:
(sorry can't include pictures yet, hopefully after this answer)
https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSession_class/Introduction/Introduction.html#//apple_ref/occ/clm/NSURLSession/sessionWithConfiguration:
The point is here...
Important
The session object keeps a strong reference to the delegate
until your app explicitly invalidates the session. If you do not
invalidate the session by calling the invalidateAndCancel or
resetWithCompletionHandler: method, your app leaks memory.
My suggestion to try is...
//Your code above...
[task resume];
[session finishTasksAndInvalidate];
}
In theory this should prevent any new sessions from starting before completion, according to the description, "...new tasks cannot be created in the session, but existing tasks continue until completion. After the last task finishes and the session makes the last delegate call, references to the delegate and callback objects are broken..."
I am still not sure about invalidating the session before resuming it.
I hope this helps. Good luck.
A developer support request to Apple reveals this to be a bug within iOS 7. There is no fault with the code sample posted above (Either recursively or iteratively) and it has reportedly been fixed in the iOS 8 GM release.
Update:
This is still occurring in iOS 8.1
I had a lot of problems with memory from NSURLSession and I finally fixed it by not using a new session for each request. Sessions are generally defined on Wikipedia as:
a semi-permanent interactive information interchange
As such, Apple's convenience class method [NSURLSession sharedSession] gives us a clue of how NSURLSession objects are intended to be used: as semi-permanent objects, not one-off objects created fresh for each request, like you are doing.
You are making a new session object per request for a ton of requests that, from the server's perspective, are all part of a single session with a single client.
I was doing the same thing until I realized this was the source of my woes. I did not find Apple's documentation on this very clear, but after I realized the error of my ways, it made certain things in the documentation suddenly make more sense, like why there is a sharedSession singleton convenience method of NSURLSession, why the word "tasks" is plural in finishTasksAndInvalidate, why they called it a "session", why it has a cache, etc. (If it was just for one request, why would it be a "session" and what good would a "cache" be?)
It helps to know how a browser like Safari looks at a session. A new session starts the first time you make a connection to a given server. Setting up the session involves creating a cache of SSL certificates, establishing authentication, handshaking, etc. It would be extraordinarily inefficient to do all of that every time some JavaScript on a page makes a new request to the same server, especially since modern web apps constantly make requests with callbacks etc. That is why a single session is established for a whole huge set of requests and responses -- a conversation, if you will, between the client and server. Eventually, a session expires, but usually this happens after several minutes, not after one request!
The point is, how you should be using NSURLSession objects is to make a singleton with a strongly referenced NSURLSession object as a property. Do this if you need to customize the session's configuration, (like turning caching off, etc.). However if you do not need to customize it, just use Apple's sharedSession.
If you use a singleton on a custom class, then, if you never need to set the session property to nil, then you never need to invalidateAndCancel or finishTasksAndInvalidate. Instead, just resetWithCompletionBlock or flushWithCompletionBlock to clear out connection caches periodically.
If you hate singletons you can still use a session as a property, just make sure to invalidateAndCancel or finishTasksAndInvalidate the session before its last owner gets deallocated by the ARC runtime.
Also note that setting your NSURLSession object's URLCache property to nil is the proper way to shut off caching. That's what Apple says they do for backgroundSessionConfiguration.
See my other answers on this topic here and here.
The only suggestions I have are perhaps using an #autoreleasepool{} and converting the __weak id self to __block id self. I don't think the __block vs. __weak will do anything differently, but give it a shot.
I'm not sure what one should expect with ARC when running something asynchronously AND recursively. Looking at other questions with asynchronous recursive calls and ARC, there isn't any consistent solution. Take a look here, for example.

Resources