Background (Asynchronous) programming in Cocoa/Objective-C - objective-c

I want something similar to C#'s BackgroundWorker.
I want to execute a block code of code (irrespective of what kind of code it is. It could be networking, I/O, complex math operations, whatever) in the background while I animate a NSProgressIndicator. Once the operation is complete, not only do I want to hide the progress indicator but I also want to receive result object(s) from the background code back to the main code.
What is the best way to do this?
Thanks for the help!

You may want to do some reading about NSOperation, NSOperationQueue, and blocks.
Using these you can execute your "block" of code asynchronously and have it "schedule" an update of the progress indicator on the main thread every so often. You can set a completion block (callback) to execute when the operation is complete and it can call a function to be executed on the main thread.
This article provides a nice overview of the options with small examples.
This answer provides an example for defining and using a completion block.

I think this library would be helpful - https://github.com/AFNetworking/AFNetworking
This library is a wrapper for Apple's API and has async tasks handled correctly. The example is shown here by Matt Thompson
How to download a file and save it to the documents directory with AFNetworking?
how to use API for downloading a resource from a url in background. This is just one example but most of them work in a similar manner to perform task in background without blocking Main UI thread
Eg :-
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"..."]];
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"filename"];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", path);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation start];

Related

upload file use uploadTaskWithRequest:fromFile: in background and use multipart/form-data

here is the situation:
my server only accept multipart/form-data upload action.
my app need to implement background upload.
I've tried below:
if I ignore background, I can use uploadTaskWithRequest:fromData:, build all boundary, content-disposition, and file data, then upload to a supported server. and I do successfully have done this. BUT I need to use background transfer.
if I force this method in background mode, I got an error: Upload tasks from NSData are not supported in background sessions.
if I use background mode, and use uploadTaskWithRequest:fromFile:, I got something like 'stream ended unexpectedly' from the server, as this question mentions, the best answer suggest people to use fromData which apparently is not my need.
so is there any way to accomplish this? since the server can't change it's support, I NEED background transportation and multipart/form-data content-type both.
Finally, I figure out it's a NSURLSession bug, from this issue, I find a way to upload file with fromFile method successfully (and many SO answers used that already, but it's still not shown in AFNetworking's document.
you just need to write your file to a temp file, and use AF's convenient method the build the multipart part. the backend reason you can find out yourself, here's my code
NSMutableURLRequest *multipartRequest = [[AFHTTPRequestSerializer serializer]
multipartFormRequestWithMethod:#"POST"
URLString:[url absoluteString]
parameters:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
[formData appendPartWithFileURL:[NSURL URLWithString:filename]
name:#"file"
fileName:short_name
mimeType:#"application/octet-stream"
error:nil];
} error:nil];
[[AFHTTPRequestSerializer serializer] requestWithMultipartFormRequest:multipartRequest writingStreamContentsToFile:[NSURL URLWithString:temp_file_name] completionHandler:^(NSError * _Nullable error) {
NSURLSessionUploadTask *task = [bgsession uploadTaskWithRequest:multipartRequest
fromFile:[NSURL URLWithString:temp_file_name]
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
[[NSFileManager defaultManager] removeItemAtPath:temp_file_name error:nil];
NSLog(#"=========response=========\n%#", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
[task resume];
}];

Crash when try to save an object inside a block. (CoreData could not fulfill a fault for...)

I'm trying to save an object inside the AFNetworking block, but SOMETIMES it crashes with error: CoreData could not fulfill a fault for...
I've read that it's related when I try to manage an object data when it was deleted from the persistent store.
(https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdTroubleshooting.html)
How can I avoid this?
Example:
Line *line = [Line MR_createEntity];
[line setSync:#(SYNC_PROCESSING)];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://test.com/request" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
// CRASH
[line setSync:#(SYNC_SUCCESS)];
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
} failure:nil];
You're trying to perform a background operation. The defaultContext is for the main thread only (it's not clearly documented, we're working on that). But what you want to do is perform your save in a context that will handle the background thread operations for you. That is, you want to use a NSManagedObjectContext that works on the background. In general try this:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
id myObject = [line MR_inContext:localContext];
[myObject setSync:#(value)];
}];
This will let MagicalRecord create the proper background context for your data to operate on when it tries to save data. MagicalRecord will set up much of the cruft needed in order to start using Core Data. Just let it do much of the work and you'll be on your way.

How to know when a for loop with NSURLSessionDataTasks is complete

I'm calling a method that will enumerate through an array, create an NSURL, and call an NSURLSessionDataTask that returns JSON. The loop typically runs about 10 times but can vary depending on the day.
I need to wait for the for loop and all NSURLSessionDataTasks to complete before I can start processing the data.
I'm having a hard time figuring out when all the work is complete. Could anyone recommend any ways or logic to know when the entire method is complete (for loop and data tasks)?
-(void)findStationsByRoute{
for (NSString *stopID in self.allRoutes) {
NSString *urlString =[NSString stringWithFormat:#"http://truetime.csta.com/developer/api/v1/stopsbyroute?route=%#", stopID];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if(httpResponse.statusCode == 200){
NSError *jsonError = [[NSError alloc]init];
NSDictionary *stopLocationDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError];
NSArray *stopDirectionArray = [stopLocationDictionary objectForKey:#"direction"];
for (NSDictionary * _stopDictionary in stopDirectionArray) {
NSArray *stop = [_stopDictionary objectForKey:#"stop"];
[self.arrayOfStops addObject:stop];
}
}
}];
[task resume];
}
}
There are a number of options. The fundamental issue is that these individual data tasks run asynchronously, so you need some way to keep track of these asynchronous tasks and establish some dependency on their completion.
There are several possible approaches:
The typical solution is to employ a dispatch group. Enter the group before you start the request with dispatch_group_enter, leave the group with dispatch_group_leave inside the completion handler, which is called asynchronously, and then, at the end of the loop, supply a dispatch_group_notify block that will be called asynchronously when all of the "enter" calls are offset by corresponding "leave" calls:
- (void)findStationsByRoute {
dispatch_group_t group = dispatch_group_create();
for (NSString *stopID in self.allRoutes) {
NSString *urlString = [NSString stringWithFormat:#"http://truetime.csta.com/developer/api/v1/stopsbyroute?route=%#", stopID];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
dispatch_group_enter(group); // enter group before making request
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if(httpResponse.statusCode == 200){
NSError *jsonError; // Note, do not initialize this with [[NSError alloc]init];
NSDictionary *stopLocationDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
NSArray *stopDirectionArray = [stopLocationDictionary objectForKey:#"direction"];
for (NSDictionary *stopDictionary in stopDirectionArray) {
NSArray *stop = [stopDictionary objectForKey:#"stop"];
[self.arrayOfStops addObject:stop];
}
}
dispatch_group_leave(group); // leave group from within the completion handler
}];
[task resume];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// do something when they're all done
});
}
A more sophisticated way to handle this is to wrap the NSSessionDataTask in a NSOperation subclass and you can then use dependencies between your data task operations and your final completion operation. You'll want to ensure your individual data task operations are "concurrent" ones (i.e. do not issue isFinished notification until the asynchronous data task is done). The benefit of this approach is that you can set maxConcurrentOperationCount to constrain how many requests will be started at any given time. Generally you want to constrain it to 3-4 requests at a time.
Note, this can also address timeout issues from which the dispatch group approach can suffer from. Dispatch groups don't constrain how many requests are submitted at any given time, whereas this is easily accomplished with NSOperation.
For more information, see the discussion about "concurrent operations" in the Operation Queue section of the Concurrency Programming Guide.
For an example of wrapping NSURLSessionTask requests in asynchronous NSOperation subclass, see a simple implementation the latter half NSURLSession with NSBlockOperation and queues. This question was addressing a different topic, but I include a NSOperation subclass example at the end.
If instead of data tasks you used upload/download tasks, you could then use a [NSURLSessionConfiguration backgroundSessionConfiguration] and URLSessionDidFinishEventsForBackgroundURLSession: of your NSURLSessionDelegate would then get called when all of the tasks are done and the app is brought back into the foreground. (A little annoyingly, though, this is only called if your app was not active when the downloads finished: I wish there was a rendition of this delegate method that was called even if the app was in the foreground when the downloads finished.)
While you asked about data tasks (which cannot be used with background sessions), using background session with upload/download tasks enjoys a significant advantage of background operation. If your process really takes 10 minutes (which seems extraordinary), refactoring this for background session might offer significant advantages.
I hate to even mention this, but for the sake a completeness, I should acknowledge that you could theoretically just by maintain an mutable array or dictionary of pending data tasks, and upon the completion of every data task, remove an item from that list, and, if it concludes it is the last task, then manually initiate the completion process.
Look at dispatch groups, for your example it will look roughly like this:
create group
for (url in urls)
enter group
start_async_task
when complete leave group
wait on group to finish or supply a block to be run when completed

How can I pause the program until nsurlconnection complete?

I know that people have asked this but I have not found satisfactory answers. I have one method that I send all my URLRequests through. I return the response of the request as a string when the method completes. I have recently added ssl to my program. This means that I can no longer use a synchronous request because I need to take advantage of the didReceiveAuthenticationChallenge function as my credentials are currently self-signing. The program needs the response from the URL in order to continue so there is not harm in waiting for the response. However, I cannot seem to find a way to just hold the code up and continue once completed. I can alert the original function that called to request function but I would like the program to pick up right after that call. And it has unique code below such calls so I cannot specialize the connectionDidFinishLoading: function because each method who calls this is different.
How can I pause the program so I can return the nsdata from the connection to the methods that called it?
Here is some pseudo-code to show you what I mean:
- (void) login:(NSString *)username :(NSString *)password {
NSString *str = [NSString stringWithFormat:%#"%#:::%#",username,password];
NSURL *url = [NSURL urlWithString:#"https://blahblahblah"];
NSString *result = [self connectToUrl:str:url];
if ([result isEqualToString:#"valid"]) {
//this would be more complex in here
NSLog(#"hooray");
} else {
NSLog(#"bummer");
}
}
- (NSString *)connectToUrl:(NSURL *)url :(NSString *)str {
NSData *FileData = [str dataUsingEncoding: NSUTF8StringEncoding];
NSMutableData *data = [[NSMutableData alloc] initWithCapacity:100];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:#"POST"];
//set up the rest of the request...
...
connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start];
//WOULD LIKE TO PAUSE HERE UNTIL COMPLETE! THEN CONTINUE
// received data is assigned in didReceiveData: method
return [[[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding] autorelease];
}
But alas, I cannot do this because I cannot make the final line wait until the connection is complete... Please help me!
Very appreciative!
R
iOS and OS X and much of the Cocoa/Cocoa touch frameworks are built on an event model. You don't pause your app. That's not the proper approach. You need to start the connection and then move on. When the connection completes, you act on that event.
In other words, your login method can't sit and wait for the result. It should start the connection and return.
When you get the result of the connection you call some method to process the login result.
Making use of blocks can make things like this easier but there are other ways. You just need to stop thinking about such things in a linear fashion. Dealing with asynchronous processing requires a different approach.

NSURLConnection sendAsynchronousRequest:queue:completionHandler not working in iOS 4.3

I am using [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) in my app. By using this my app is terminated in iOS 4.3 but it is working fine in iOS 5.0.
How to use this in iOS 4.3 can any one help me.
Here's a full implementation that works for me. Feel free to rename it and add as a category on NSURLConnection, or just add it as a local method in the class you're working in.
-(void)sendAsynchronousRequest:(NSURLRequest*)request queue:(NSOperationQueue*)queue completionHandler:(void(^)(NSURLResponse *response, NSData *data, NSError *error))handler
{
__block NSURLResponse *response = nil;
__block NSError *error = nil;
__block NSData *data = nil;
// Wrap up synchronous request within a block operation
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
}];
// Set completion block
// EDIT: Set completion block, perform on main thread for safety
blockOperation.completionBlock = ^{
// Perform completion on main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
handler(response, data, error);
}];
};
// (or execute completion block on background thread)
// blockOperation.completionBlock = ^{ handler(response, data, error); };
// Execute operation
[queue addOperation:blockOperation];
}
EDIT
I had to modify the method because I was making UIKit calls in my completion block (e.g. updating labels etc). So it's actually a bit safer to call completion block on the main thread. (original version commented out)
The method you are trying to use is only available on iOS 5. For earlier OSes, consider using
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
and wrapping it into a new thread to achieve async behavior.
Both H2CO3 and Ken Thomases suggestions are right.
In addition, you could take a look at ios4-implementation-of-nsurlconnection-sendasynchronousrequestqueuecompletio.
If you use the main queue as the queue where the completion handler performs, you could use (as Tom suggested) the delegate pattern. To avoid duplicate code, you could use a wrapper on NSURLConnection delegates mechanism.
In the other case, if you want to maintain the async behaviour and you don't want to deal with sync call (as H2CO3 suggested, note that his suggestion is also valid) and the completion handler performs in a different queue, then I suggest you to wrap the async delegate pattern in a NSOperation class. This approach is quite difficult but you can find a good way of do this in Concurrent Operations Demystified (see both the posts).
Hope it helps.