In my app when the user presses a button I start a HTTP asynchronous request (using AFURLConnectionOperation) and change the text of UILabel in the completionHandler block. This change, however, does not take place when the request is concluded and instead happens around 2-3 seconds later. Below is a code snippet that results in this behavior.
AFURLConnectionOperation* operation = ...
[opration setCompletionBlock:^{
NSLog(#"This text appears immediatly");
[myLabel setText:#"this one is delayed for 2-3 sec"];
}];
[opreation start];
tahnks for help
This is symptomatic of attempts to try to perform UI updates from background queue. Your problem will be resolved if you add that UI update back to the main queue:
[operation setCompletionBlock:^{
NSLog(#"This text appears immediately");
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[myLabel setText:#"this should not be delayed for 2-3 sec"];
}];
}];
As the NSOperation documentation for setCompletionBlock says, you have no assurances of what thread the completion operation will take place:
The exact execution context for your completion block is not guaranteed but is typically a secondary thread. Therefore, you should not use this block to do any work that requires a very specific execution context. Instead, you should shunt that work to your application’s main thread or to the specific thread that is capable of doing it. ...
UI updates must take place on the main queue, so if your completion block wants to perform UI updates, it must explicitly add them to the main queue.
AFURLConnectionOperation is a subclass of NSOperation.
Further, by looking at AFURLConnectionOperation.m, we can see that setCompletionBlock: basically just calls its super method (of course, it also takes care of locking and nicely setting the completionBlock property as nil for you when done).
Important note: AFURLConnectionOperation does NOT perform the completionBlock on the main thread for you. NSOperation is also not guaranteed to perform the completionBlock on the main thread either.
However, user interface (UI) updates must happen on the main thread (otherwise, unexpected things may happen).
To fix this, you need to make sure your UI updates are happening on the main thread. In example, you could do something like this:
[operation setCompletionBlock:^{
NSLog(#"This text appears immediately");
dispatch_async(dispatch_get_main_queue(), ^{
// do your UI updates here...
});
}];
Related
Every time I make an API call to my server to get data, I already know that I have to use the following block to execute UI changing commands because my API call executes in the background thread:
dispatch_async(dispatch_get_main_queue(), ^{
//do UI stuff
});
However, what if I have a function that does UI changing stuff outside of the API call block? For example:
-(void)doALotOfUIChanging
{
//do a lot of UI changing
}
In my API call block, do I need to call that UI changing function in the main thread like so?:
[apiObject getDataFromObject:my.Object successCallback:^(Array *data)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self doALotOfUIChanging];
});
}
errorCallback:^(NSString *error)
{
NSLog(#"%#", error);
}];
Or do I not have to call it in the main thread since the function is already outside of the API call block like so?:
[apiObject getDataFromObject:my.Object successCallback:^(Array *data)
{
[self doALotOfUIChanging];
}
errorCallback:^(NSString *error)
{
NSLog(#"%#", error);
}];
I also have functions that perform segues to other view controllers, so I'm also wondering if I should call them in the main thread as well. I'm doing some code clean up and I don't want to have to constantly rewrite the dispatch_async function in situations that I might not have to, so any help or advice would be greatly appreciated. Thanks.
Short answer: Yes you should update your UI on main thread.
Threads and Your User Interface
If your application has a graphical user interface, it is recommended
that you receive user-related events and initiate interface updates
from your application’s main thread. This approach helps avoid
synchronization issues associated with handling user events and
drawing window content. Some frameworks, such as Cocoa, generally
require this behavior, but even for those that do not, keeping this
behavior on the main thread has the advantage of simplifying the logic
for managing your user interface.
There are a few notable exceptions where it is advantageous to perform
graphical operations from other threads. For example, you can use
secondary threads to create and process images and perform other
image-related calculations. Using secondary threads for these
operations can greatly increase performance. If you are not sure about
a particular graphical operation though, plan on doing it from your
main thread.
Reference:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/AboutThreads/AboutThreads.html
First of all you should never use self inside a block . You can use __weak yourClassName *weakSelf = self instead.
Regarding your problem, all UI changes should be done on main thread. So you need to do this :
[apiObject getDataFromObject:my.Object successCallback:^(Array *data)
{
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf doALotOfUIChanging];
});
}
Hope it helps. :)
I have studied Apple documentation about asynchronous operation and I am unable to get it properly.
I am sharing my understanding and efforts. Please take a look and suggest me for understanding asynchronous NSOperation properly.
Asynchronous is a readonly property and we can't change it.
I have created a class that inherits NSOperation. I have override start and main method also.
I am not using NSOperationQueue. When I start operation using [operation start]; method on main thread. Then in start and main method implementation, I get isAsynchronous '0'.
When I start operation on secondary thread using
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
operation = [[CustomOperation alloc] init];
[operation start];
}
I still getting isAsynchronous '0' in start and main method implementation.
I am not getting why isAsynchronous always returns '0' in any thread. Please let me know the reason behind this.
I saw some questions were asked based on this problem but I couldn't understand this functionality.
It would be helpful for me if you give me any example so that I can understand it properly.
Please let me know if I am not clear enough and I should describe it more.
You are mixing up Grand Central Dispatch and NSOperation.
By default, an NSOperation is synchronous - when you start it, it runs on the current thread until it finishes. You are starting a synchronous NSOperation on a background thread, so it will run on that background thread until it finishes. Which is no problem at all. It runs in the background. It doesn't call itself "asynchronous" because it doesn't care about threads.
Most of the time you are much better off just using Grand Central Dispatch and not using NSOperation at all. The point where you use NSOperation is when you need it to be cancellable.
NSBlockOperation *operation_6 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"66666666666");
NSLog(#"66==%#",[NSThread currentThread]);
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[operation_6 start];
});
log:[7052:1932587] 66==<NSThread: 0x7fd18e73ef60>{number = 2, name = (null)}
I have an app that uses a connection queue that handles the connections on a background thread. Each connection sends a JSON post, then when it receives a success, saves some objects into coredata.
Once all connections are complete, i call a dispatch_async on the main thread to call a finished method.
However, under very specific conditions of data im sending/saving, I've noticed the dispatch_async block to the main thread never gets called, and the app screen freezes, all execution stops, and the app sits idle with a frozen screen. processing power according to xcode is 0%.
Here is method with the block that fails.
- (void)connectionDidComplete
{
_completeConnections++;
_syncProgress = (float)_completeConnections / (float)_totalConnections;
dispatch_async(mainQueue, ^(void) {
[[NSNotificationCenter defaultCenter] postNotificationName:SyncQueueDidUpdateNotification object:nil];
}); <-- this dispatch works
if (_completeConnections == _totalConnections)
{
// clear unsynced data
NSArray *syncedObjects = [SyncObject completedSyncObjects];
if (syncedObjects.count > 0)
{
for (SyncObject *syncObject in syncedObjects)
{
[syncObject delete];
}
}
//this method saves the current context, then merges this context with the main context right after
[[VS_CoreDataManager sharedManager] saveManagedObjectContextAndWait:managedObjectContext];
// cleanup the thread's context
[[VS_CoreDataManager sharedManager] unRegisterManagedObjectContextForThread:currentThread];
managedObjectContext = nil;
// complete sync
dispatch_async(mainQueue, ^(void) {
[self performSelector:#selector(finishSync) withObject:nil afterDelay:2];
}); <-- this dispatch never gets called
}
}
My suspicion is this problem has something to do with saving the context then merging it. And possibly while that is happening its released in the middle of the merge, causing some weird hang up and the dispatch isn't getting executed. This is just a guess though, and I don't know how to fix it.
Any ideas?
Thanks.
If the block on the main thread is not executed, then it is because of 1 of 2 reasons.
The main thread is blocked; is not processing any events at all. Got a while() loop on the main thread? That'd do it. A lock? There you go.
The main thread is running a modal run loop inside the outer run loop. Asynchronous dispatches to the main event loop -- main thread -- won't be processed in this case.
Set a breakpoint on that dispatch_async() and see what the main thread is doing (at the point of dispatch the main thread is most likely already in the bad state).
DarkDust's suggestion of using dispatch_after() is a good one, but is unlikely to work in that it is almost assuredly the case that your main thread is not processing events when the problem occurs. I.e. fix the problem, then move to dispatch_after() as DarkDust suggests.
If your main thread is busy with modal runloop, then you could try
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, block
});
I believe this is a great discussion. I came across this when I had the following code:
dispatch_synch(dispatch_get_main_queue()){
print("I am here")
}
the print code did not execute as I was dispatching a 'synch' block on the serial main thread which caused a dead lock. print was waiting for the dispatch to finish and dispatch was waiting for print to finish. When you dispatch in the main serial queue then you should use dispatch_async. and i guess if you use a concurrent queue then dispatch synch suits better
I created an NSOperation subclass to handle some zip archive operations. No matter what, if I override -start or -main this block of code always happens:
if ([NSThread isMainThread]) {
NSLog(#"I am in the main thread");
return;
}
Any idea what's going on?
I've tried adding this block:
- (void) start { //also tried overriding main
if ([NSThread isMainThread]) {
NSLog(#"In main thread, trying again");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self start];
});
return;
//hard working code etc...
//cpu intensive zip operations...
}
But this causes a crash, an EXC_BAD_ACCESS violation pointing at the dispatch_async line.
No matter what, if I override -start or -main this block of code always happens:
The main operation queue runs on the main thread. From the docs for +[NSOperationQueue mainQueue]:
The returned queue executes operations on the main thread. The main
thread’s run loop controls the execution times of these operations.
So, running in another thread is a matter of what queue you add the operation to, not how you write the operation's code. If you want your operation to run on a different operation queue, you'll need to create a queue of your own using
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
You can find an example in Adding Operations to an Operation Queue in the Concurrency Programming Guide.
But this causes a crash, an EXC_BAD_ACCESS violation pointing at the dispatch_async line.
It sounds like -[NSOperation start] probably isn't re-entrant. Your code effectively executes the same method on two different threads. In fact, look at the docs for -start, it's obvious that your code won't work:
You can call this method explicitly if you want to execute your
operations manually. However, it is a programmer error to call this
method on an operation object that is already in an operation queue
or to queue the operation after calling this method. Once you add an
operation object to a queue, the queue assumes all responsibility for
it. [Emphasis added. -Caleb]
In other words, don't do that.
I'm googling like crazy and still confused about this.
I want to download an array of file urls to disk, and I want to update my view based on the bytes loaded of each file as they download. I already have something that will download a file, and report progress and completion via blocks.
How can I do this for each file in the array?
I'm ok doing them one at a time. I can calculate the total progress easily that way:
float progress = (numCompletedFiles + (currentDownloadedBytes / currentTotalBytes)) / totalFiles)
I mostly understand GCD and NSOperations, but how can you tell an operation or dispatch_async block to wait until a callback is called before being done? It seems possible by overriding NSOperation, but that seems like overkill. Is there another way? Is it possible with just GCD?
I'm not sure if I understand you correctly, but perhaps you need dispatch semaphores to achieve your goal. In one of my projects I use a dispatch semaphore to wait until another turn by another player is completed. This is partially the code I used.
for (int i = 0; i < _players.count; i++)
{
// a semaphore is used to prevent execution until the asynchronous task is completed ...
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// player chooses a card - once card is chosen, animate choice by moving card to center of board ...
[self.currentPlayer playCardWithPlayedCards:_currentTrick.cards trumpSuit:_trumpSuit completionHandler:^ (WSCard *card) {
BOOL success = [self.currentTrick addCard:card];
DLog(#"did add card to trick? %#", success ? #"YES" : #"NO");
NSString *message = [NSString stringWithFormat:#"Card played by %#", _currentPlayer.name];
[_messageView setMessage:message];
[self turnCard:card];
[self moveCardToCenter:card];
// send a signal that indicates that this asynchronous task is completed ...
dispatch_semaphore_signal(sema);
DLog(#"<<< signal dispatched >>>");
}];
// execution is halted, until a signal is received from another thread ...
DLog(#"<<< wait for signal >>>");
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
DLog(#"<<< signal received >>>");
dispatch groups are the GCD facility designed to track completion of a set of independent or separately async'd blocks/tasks.
Either use dispatch_group_async() to submit the blocks in question, or dispatch_group_enter() the group before triggering the asynchronous task and dispatch_group_leave() the group when the task has completed.
You can then either get notified asynchronously via dispatch_group_notify() when all blocks/tasks in the group have completed, or if you must, you can synchronously wait for completion with dispatch_group_wait().
I just wanted to note that I did get it working by subclassing NSOperation and making it a "concurrent" operation. (Concurrent in this context means an async operation that it should wait for before marking it as complete).
http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/
Basically, you do the following in your subclass
override start to begin your operation
override isConcurrent to return YES
when you finish, make sure isExecuting and isFinished change to be correct, in a key-value compliant manner (basically, call willChangeValueForKey: and didChangeValueForKey: for isFinished and isExecuting
And in the class containing the queue
set the maxConcurrentOperationCount on the NSOperationQueue to 1
add an operation after all your concurrent ones which will be triggered once they are all done