Dealing with Blocks, completion handlers, dispatch_async vs dispatch_sync - iphone

I'm executing an online fetch of data in a thread and I want to do something immediately after the block is executed.
Here's my code:
- (IBAction)refresh:(UIBarButtonItem *)sender {
NSLog(#"checking");
[self editToolbar];
dispatch_queue_t fetchQ = dispatch_queue_create("Refreshing", NULL);
dispatch_async(fetchQ, ^{
[self setupFetchedResultsController];
[self fetchImonggoItemsDataIntoDocument: self.itemDatabase];
});
dispatch_release(fetchQ);
NSLog(#"done checking");
//do something here
}
The thing is dispatch_async returns immediately and "done checking" prints immediately even before the block is done executing. How do I solve this?

I think it's an architectural issue. The tasks are something like:
edit toolbar
fetchImonggoItemsDataIntoDocument
do something else
If these must be done exactly in order then I don't quite understand the use of blocks or queues; just run the statements after each other and you'll be set.
Otherwise, alternative #1 would be to use dispatch_sync rather than dispatch_async. Again, I'm not quite sure what the benefit of using a queue would be but there it is.
Alternative #2 would be to use a callback from the block. Something like:
- (IBAction)refresh:(UIBarButtonItem *)sender {
NSLog(#"checking");
[self editToolbar];
dispatch_queue_t fetchQ = dispatch_queue_create("Refreshing", NULL);
dispatch_async(fetchQ, ^{
[self setupFetchedResultsController];
[self fetchImonggoItemsDataIntoDocument: self.itemDatabase];
[self doneChecking]; // <-- NOTE! call the callback
});
dispatch_release(fetchQ);
}
// NOTE! refresh: has been split up into two methods
- (void)doneChecking:
NSLog(#"done checking");
//do something here
}

As others have already suggested, this is probably what you need.
NSArray *items = [iMonggoFetcher fetchImonggoData:IMONGGO_GENERIC_URL_FOR_PRODUCTS withFormat:#"json" withDateRangeArgs:args];
[document.managedObjectContext performBlock:^{
for (NSDictionary *itemInfo in items){
[Product productWithImonggoInfo:itemInfo inManagedObjectContext:document.managedObjectContext];
}
// Put here what you need :)
}];

Related

Wait for URLConnection block to finish

I'm creating a REST client class for my iPad app. So I created a BOOL method which does the login using an NSURLConnection subclass I created earlier.
This JWURLConnection has block type properties for the finishLoading and failWithError operations.
The Problem is that the URL connection most likely finishes (or fails) AFTER this method is completely executed. A cannot use an extra method to use performSelector:waitUntilDone: too because I have to wait for the connection.
Now I tried using plain C semaphores and an extra thread (so that the semaphore blocks only the RESTClient thread, not the URLConnections one), but I had no success; the method started waiting but the whole connection stuff was frozen, thus there where no NSLogs from the connection.
The JWURLConnection starts it's own thread by itself within the -start method:
- (void)start { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [super start]; }); }
Here is the code I tried it with (using semaphores):
- (BOOL)loginWithUsername:(NSString *)uName ansPassword:(NSString *)pWord {
__block BOOL loginSucceeded = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
JWURLConnection *connection = [JWURLConnection connectionWithPOSTRequestToURL:POSTData:];
[connection setFinished^(NSData *data) {
// validate server response and set login variable
loginSucceeded = YES;
dispatch_semaphore_signal(sema);
}];
[connection setFailed:^(NSError *error) {
loginSucceeded = NO;
NSLog(#"Login failed: %#", [error description]);
dispatch_semaphore_signal(sema);
}];
[connection start];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
// do some more stuff like error handling / reporting here
return loginSucceeded;
}
I hope you can lead my the right direction...
The JWURLConnection starts it's own thread by itself within the -start method:
- (void)start { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [super start]; }); }
You need to ensure that a NSURLConnection's delegate methods will be scheduled on a NSRunLoop or a NSOperationQueue. While the start method could actually take care of this - the given code and your comment indicate it does not ;) In short, dispatch_async does not guarantee that the underlaying thread has a run loop and a dispatch queue does not even guarantee that the underlaying thread is always the same.
The docs show how to schedule a connection.
I would suggest to schedule the connection on the main thread, and change this to a NSOperationQueue when required.
Your loginWithUsername:andPassword: method will simply return immediately since you call/invoke an asynchronous function/method.
Employing asynchronous patterns is kinda "infectious". Once you started using asynchronous programming style, you cant get "rid of" it unless you use synchronization primitives that block the current thread. I would suggest to keep the async style:
- (void) loginWithUsername:(NSString *)uName
andPassword:(NSString *)pWord
completion:(void(^)(id result))onCompletion;
And later:
[self loginWithUsername:#"Me" andPassword:#"secret" completion:^(id result) {
if ([result != [isKindOfError class]]) {
[self fetchImagesWithURL:url completion: ^(id result) {
...
}];
}
}];

Updating label on the main thread is not working

I'm trying to update a label while different tasks are proceeding. I searched and used different options and endup using this way but it still doesn't work:
[processStatusLable performSelectorOnMainThread:#selector(setText:) withObject:#"Creating your account..." waitUntilDone:NO];
DCConnector *dccon = [DCConnector new];
ContactsConnector *conCon = [ContactsConnector new];
if (![dccon existUsersData]) {
[dccon saveUsersInformation:device :usDTO];
//created account
//get friends -> Server call
[processStatusLable performSelectorOnMainThread:#selector(setText:) withObject:#"Checking for friends..." waitUntilDone:NO];
NSMutableArray *array = [conCon getAllContactsOnPhone];
// save friends
[processStatusLable performSelectorOnMainThread:#selector(setText:) withObject:#"Saving friends.." waitUntilDone:NO];
if ([dccon saveContacts:array]) {
[processStatusLable performSelectorOnMainThread:#selector(setText:) withObject:#"Friends saved successfully.." waitUntilDone:NO];
}
}
The last performSelector is getting executed (at least I see the label text changed on the view), but all other selectors are not working. Any idea why?
EDIT 1
- (void)updateLabelText:(NSString *)newText {
processStatusLable.text = newText;
}
we can use the following code to run something on the main thread,
dispatch_async(dispatch_get_main_queue(), ^{
//set text label
});
Using that we can write a method like this,
- (void)updateLabelText:(NSString *)newText {
dispatch_async(dispatch_get_main_queue(), ^{
processStatusLable.text = newText;
});
}
Finally, you can use change your code this way,
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self updateLabelText:#"Creating your account..."];
DCConnector *dccon = [DCConnector new];
ContactsConnector *conCon = [ContactsConnector new];
if (![dccon existUsersData]) {
[dccon saveUsersInformation:device :usDTO];
//created account
//get friends -> Server call
[self updateLabelText:#"Checking for friends..."];
NSMutableArray *array = [conCon getAllContactsOnPhone];
// save friends
[self updateLabelText:#"Saving friends.."];
if ([dccon saveContacts:array]) {
[self updateLabelText:#"Friends saved successfully.."];
}
}
});
How fast do you run through this sequence of updates? If it is faster than a second, you aren't likely going to see all of 'em.
Making them wait until done is unlikely to impact anything as the drawing is done asynchronously anyway.
Note that your method names are unconventional; methods shouldn't be prefixed with get and saveUsersInformation:: is discouraged (try something like saveUsersInformationToDevice:usingDTO:).
How much time elapses between the calls to update the text field? The whole process takes a minute, but how is that time divided?
What is your main event loop doing otherwise? Running modally or running normally?

GCD flow how to write

I am trying to make screen shot of avplayer when video start playing so i need to run this code in fast in background so it will not block main thread and other controls run fast simultaneous,trying to run that code GCD format i am not able to run please help me to do that it stops at where i add into my array(in array i am adding UIImage Object)...
if (isCaptureScreenStart)
{
if (CMTimeGetSeconds([avPlayer currentTime])>0)
{
if (avFramesArray!=nil)
{
queue = dispatch_queue_create("array", NULL);
dispatch_sync(queue, ^{
[avFramesArray addObject:[self screenshotFromPlayer:avPlayer maximumSize:avPlayerLayer.frame.size :CMTimeGetSeconds([avPlayer currentTime])]];//stop at this line
NSLog(#"count:%d",[avFramesArray count]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"Frame are created:%d",[avFramesArray count]);
if ([avFramesArray count]==0)
{
NSLog(#"Frame are over");
}
});
});
}
}
}
dispatch_release(queue);
Edit:
I think i need to use dispatch_group_async this block now..please give some guideline that how to use:
if (isCaptureScreenStart)
{
if (CMTimeGetSeconds([avPlayer currentTime])>0)
{
if (avFramesArray!=nil) {
dispatch_group_async(serial_group1, serial_dispatch_queue1, ^{
[avFramesArray addObject:[self screenshotFromPlayer:avPlayer maximumSize:avPlayerLayer.frame.size :CMTimeGetSeconds([avPlayer currentTime])]];
});
}
}
dispatch_group_notify(serial_group1, serial_dispatch_queue1, ^{
NSLog(#"task competed");
});
}
Now I am using this block but above execution is contentious running and if i use dispatch_suspend(serial_dispatch_queue1); its stop but again i need to start block execution then what i need to use i have also try with dispatch_resume(serial_dispatch_queue1); again load but system show me crash
dispatch_release(queue); don't do it there, the dispatch queue that you are calling its going to a backThread, so wat is happening is :-
your queue is getting released before the block of code executes.
since your queue looks like an ivar, release it in dealloc. Rest, your code looks fine ..put a breakpoint inside and check if the block is executing.
EDIT
I dont understand, what u are trying to achieve by suspending the queue, there is no need to do it. You dont need to check whether the block has finished executing. The block will finish and then call the dispatch_async , get the main queue and update the UI from there.
Now, when you are creating the queue, create it lazily in your method. take the queue as an ivar in header file:
#interface YourFileController : UIViewController {
dispatch_queue_t queue;
}
Then in your method modify it as such:
if (isCaptureScreenStart)
{
if (CMTimeGetSeconds([avPlayer currentTime])>0)
{
if (avFramesArray!=nil)
{
if (!queue)
queue = dispatch_queue_create("array", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
[avFramesArray addObject:[self screenshotFromPlayer:avPlayer maximumSize:avPlayerLayer.frame.size :CMTimeGetSeconds([avPlayer currentTime])]];//stop at this line
NSLog(#"count:%d",[avFramesArray count]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"Frame are created:%d",[avFramesArray count]);
if ([avFramesArray count]==0)
{
NSLog(#"Frame are over");
}
});
});
}
}
}
NOTE : DISPATCH_QUEUE_SERIAL creates a serial queue, meaning all the blocks submitted to it will execute serially in First in First Out order. Once all the blocks submitted get executed, the queue stays ;) ..submit another block to it and it executes the block :D
this represents one whole block:-
[avFramesArray addObject:[self screenshotFromPlayer:avPlayer maximumSize:avPlayerLayer.frame.size :CMTimeGetSeconds([avPlayer currentTime])]];//stop at this line
NSLog(#"count:%d",[avFramesArray count]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"Frame are created:%d",[avFramesArray count]);
if ([avFramesArray count]==0)
{
NSLog(#"Frame are over");
}
});

function return value from block

What if I have a function that returns an int and the return value of the int is taken from the block?
For example:
- (int) queryForKey:(NSString *)aKey view:(UIButton *)aView countView:(UIView *)aCountView counter:(int) count {
//some initialization
[query countObjectsInBackgroundWithBlock:^(int number, NSError * error) {
[aCountView addSubview:self.generateCountLabel];
if (number > 0){
[aView setUserInteractionEnabled:YES];
[aView setEnabled:YES];
}
//return number; //doing this will generate an error
}];
}
also another question is, what if I have an int passed in as an argument of the function above and I would like to assign a value to it. Is some thing like that even possible?
Well your block does not have a return value, it returns void.
To return a value you could use the __block modifier on a variable outside your block and store then answer there which can then be used by the rest of your method (or code).
The problem is that you have a synchronous method (one that wants to return the value immediately) that needs to return a value derived from an asynchronous method (one that goes about it's business in a different thread).
There are a couple of ways of fixing this:
wait for the countObjectsInBackgroundWithBlock: method to complete, use the __block pattern as #simonpie described.
replace the return number; with a call to something somewhere interested in the resulting number. This also means that queryForKey:view:countView: will likely return void.
The latter is the preferred solution as it will not block the thread calling the queryForKey:... method.
Note that you can't diddle UIKit classes on anything but the main thread. If that block is executed on a background thread, then doing what you are doing in the block is invalid.
I've found a better solution. Hopefully this will help someone else who stumbles across the question. I would implement a solution to your code like so:
- (int) queryForKey:(NSString *)aKey view:(UIButton *)aView countView:(UIView *)aCountView counter:(int) count {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
__block int number;
//some initialization
[query countObjectsInBackgroundWithBlock:^(int number, NSError * error) {
dispatch_async(queue, ^{
[aCountView addSubview:self.generateCountLabel];
if (number > 0){
[aView setUserInteractionEnabled:YES];
[aView setEnabled:YES];
}
dispatch_semaphore_signal(sema);
});
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return number; //doing this will no longer generate an error
}
Then encapsulate your call with another dispatch_async so that your semaphore wait call doesn't block the main thread.
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
[self queryForKey:#"AKey" view:myView countView:myCountView counter:aCounter];
});

Using an application-lifetime-thread other than the main thread

I've a multi-threading application in which each thread has to do some job, but at a certain point some code needs to be executed serially (like writing into sqlite3 database), so I'm calling that code to be performed on main thread using:
[self performSelectorOnMainThread:#selector(serialJob:) withObject:object waitUntilDone:YES];
and every thing went just fine except that when that code needs some time the user interaction with the application gets disabled until that code has been finished, so is there any way to make another ONE thread that can be run on background and can be called whenever I need it just like the main one so I can replace the previous call with:
[self performSelector:#selector(serialJob:) onThread:REQUIRED_THREAD withObject:object waitUntilDone:YES];
this thread should be some class's static data member to be accessed from all over the code.
any help would be very appreciated, and many thanks in advance...
This is quite easy to do, just spawn your thread and let it run it's runloop using [[NSRunLoop currentRunLoop] run]. That's all that is required to be able to use performSelector:onThread: with a custom thread.
If you are on iOS 4 or newer you should consider using Grand Central Dispatch queues instead of threads though. The GCD APIs are much easier to use and can utilize the system resources much better.
Like Sven mentioned, look into Grand Central Dispatch.
You can create a queue like this:
dispatch_queue_t myQueue = dispatch_queue_create("com.yourcompany.myDataQueue", NULL);
Now you can call blocks on that queue:
dispatch_async(myQueue, ^{
// Your code to write to DB.
});
When you're done, don't forget to release the queue:
dispatch_release(myQueue);
Due to the my question that I need the current thread to be blocked until the database job has been finished, I've tried these two solutions and they worked perfectly. You can either use critical sections or NSOperationQueue and I prefer the first one, here is the code for both of them:
define some class "DatabaseController" and add this code to its implementation:
static NSString * DatabaseLock = nil;
+ (void)initialize {
[super initialize];
DatabaseLock = [[NSString alloc] initWithString:#"Database-Lock"];
}
+ (NSString *)databaseLock {
return DatabaseLock;
}
- (void)writeToDatabase1 {
#synchronized ([DatabaseController databaseLock]) {
// Code that writes to an sqlite3 database goes here...
}
}
- (void)writeToDatabase2 {
#synchronized ([DatabaseController databaseLock]) {
// Code that writes to an sqlite3 database goes here...
}
}
OR to use the NSOperationQueue you can use:
static NSOperationQueue * DatabaseQueue = nil;
+ (void)initialize {
[super initialize];
DatabaseQueue = [[NSOperationQueue alloc] init];
[DatabaseQueue setMaxConcurrentOperationCount:1];
}
+ (NSOperationQueue *)databaseQueue {
return DatabaseQueue;
}
- (void)writeToDatabase {
NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(FUNCTION_THAT_WRITES_TO_DATABASE) object:nil];
[operation setQueuePriority:NSOperationQueuePriorityHigh];
[[DatabaseController databaseQueue] addOperations:[NSArray arrayWithObject:operation] waitUntilFinished:YES];
[operation release];
}
these two solutions block the current thread until the writing to database is finished which you may consider in most of the cases.