What's the difference between performSelectorOnMainThread: and dispatch_async() on main queue? - objective-c

I was having problems modifying a view inside a thread. I tried to add a subview but it took around 6 or more seconds to display. I finally got it working, but I don't know how exactly. So I was wondering why it worked and what's the difference between the following methods:
This worked -added the view instantly:
dispatch_async(dispatch_get_main_queue(), ^{
//some UI methods ej
[view addSubview: otherView];
}
This took around 6 or more seconds to display:
[viewController performSelectorOnMainThread:#selector(methodThatAddsSubview:) withObject:otherView
waitUntilDone:NO];
NSNotification methods - took also around 6 seconds to display the observer was in the viewController I wanted to modify paired to a method to add a subview.
[[NSNotificationCenter defaultCenter] postNotificationName:
#"notification-identifier" object:object];
For reference these were called inside this CompletionHandler of the class ACAccountStore.
accountStore requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) {
if(granted) {
// my methods were here
}
}

By default, -performSelectorOnMainThread:withObject:waitUntilDone: only schedules the selector to run in the default run loop mode. If the run loop is in another mode (e.g. the tracking mode), it won't run until the run loop switches back to the default mode. You can get around this with the variant -performSelectorOnMainThread:withObject:waitUntilDone:modes: (by passing all the modes you want it to run in).
On the other hand, dispatch_async(dispatch_get_main_queue(), ^{ ... }) will run the block as soon as the main run loop returns control flow back to the event loop. It doesn't care about modes. So if you don't want to care about modes either, dispatch_async() may be the better way to go.

It's likely because performSelectorOnMainThread:withObject:waitUntilDone: queues the message with common run loop modes. According to Apple's Concurrency Programming Guide, the main queue will interleave queued tasks with other events from the app's run loop. Thus, if there are other events to be processed in the event queue, the queued blocks in the dispatch queue may be run first, even though they were submitted later.
This article is a superb explanation to performSelectorOnMainThread vs. dispatch_async, which also answers the above question.

Did you try thePerformSelectorOnMainThread with waitUntilDone=YES
Eg:
Code:
[viewController performSelectorOnMainThread:#selector(methodThatAddsSubview:) withObject:otherView waitUntilDone:YES];
I think that might solve the issue as of why the PerformSelectorOnMainThread takes so long to respond.

Related

dispatch_semaphore_wait not triggered after timeout

I want to build an NSOperation that has a timeout of 10 seconds after it begins and could be ended by another thread at any point through an event. I also use an NSOperationQueue for managing more operations like this and it can only compute one at a time(maxConcurrentOperationCount = 1). For this I have thought about an implementation using dispatch_semaphore's as it follows:
#implementation CustomOperation
dispatch_semaphore_t semaphore;
-(void) main {
#autoreleasepool {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(shouldFinishWaiting:) name:#"myCustomEvent" object:nil];
semaphore = dispatch_semaphore_create(0);
[self doStuff];
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)));
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"myCustomEvent" object:nil];
}
}
-(void) shouldFinishWaiting {
NSLog(#"[WatchOperation]: Should finish waiting! %#", self);
dispatch_semaphore_signal(semaphore);
}
#end
The problem I have is that once in many times when an user starts the application the first operation would not finish until the event gets triggered(and this could happen after 30 mins). The timeout would not be taken in consideration. I noticed this on logs from some users so I wasn't able to reproduce it. What could go wrong so that the dispatch_semaphore_wait fails to execute?
Later edit: I mistakenly thought that the -doStuff is async. It seems it is not.I replaced it with:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
[self doStuff];
});
, but the operation was already on the serial queue 'user initiated'. As I can see it creates another concurent thread, will that happen every time? Is this safe?
I don't think dispatch semaphore can go wrong. Maybe your -doStuff is taking too much time. Make sure you are doing the following:
1. The method [self doStuff]; is async and it dispatches to a DIFFERENT thread than the current one (dispatching to current thread doesn't really make sense if you want the 10 second timeout using the semaphore).
2. Make sure you keep checking for self.isCancelled in -doStuff.
Also, I would suggest that you take a slightly different design approach for your requirements (if I understand them correctly) --
1. Your NSOperation can always be cancelled from any external thread my calling cancel on the object, so there is no need for a complex NSNotification-based approach, just check for isCancelled and override the -cancel method.
2. For the 10 second timeout, you can use the semaphore approach, but just have a DIFFERENT thread doing the semaphore wait. That thread can then cancel your task from within after 10 seconds.
I assume your semaphore won't stay a global variable...
Depending on whether you are using 32 or 64 bit, NSEC_PER_SEC could be a 32 bit value, which overflows and turns into something negative when multiplied by 10. Replace 10 by 10.0. Might fix the problem, might not do anything at all.
#selector(shouldFinishWaiting:) should be #selector (shouldFinishWaiting). No colon because the method has no arguments.

dispatch_async doesn't work as advertised

I'm still pretty green with iOS, but the code below seems like it should work. I have a bunch of work to do in the background so a "busy indicator" is supposed to be shown to the user while the work is done. But what happens is the busy indicator does not appear for several seconds. It only appears once most of the work (but not all) has completed.
- (void) fetchDataTest {
// next line will create and display a busy indicator
__block MRProgressOverlayView *overlay = [MRProgressOverlayView showOverlayAddedTo:self.view title:#"Loading…" mode:MRProgressOverlayViewModeIndeterminate animated:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^(void) {
[self fetchData];
dispatch_async(dispatch_get_main_queue(), ^(void) {
[overlay hide:YES];
});
});
}
I have lots of logging (not shown above) that runs so slowly I can watch the output log (including thread ids) and see overlay has been created and background work started - yet no overlay on my device appears for several seconds.
How can I get the overlay to appear immediately?
Observations:
you shouldn't use GCD to make synchronous data fetches look asynchronous — just use the asynchronous fetches directly. They're much better for overall scheduling and for battery life;
it's the bit that occurs before the dispatch_asyncs that isn't happening, not the stuff within them;
Jack Wu's guess is probably right: on what thread or queue is fetchDataTest being performed? It should be the main queue.
On the last point: UIKit is not generally thread safe. With a few exceptions, you should always use it from the main queue. That's a large part of what the main queue is for nowadays: all user interaction. Then heavy work should be passed off elsewhere.

IOS Grand Central Dispatch with callback method

I haven't used GCD or much threading in my apps but I've run into a situation where I need to run a method or two off another thread. Once this method completes I need to call another method using the main thread from a callback. I've been searching around to see how to detect when a thread has finished the operation but still not too clear on the subject.
I created a test app and just used the viewDidLoad method for a quick example.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSLog(#"viewDidLoad called");
sleep(5); // simulating a thread being tied up for 5 seconds
dispatch_async(dispatch_get_main_queue(), ^{
[self callbackMethod]; // method called after above thread has completed running
});
});
}
Will this example work for what I'm trying to do? When running the application it appears that the callback method is called after the sleep(5) finishes. Is this the proper way of handling this situation or am I way off course?
You're spot on; that's the standard pattern for getting off and on the main thread. See my answer here: https://stackoverflow.com/a/13080519/341994
And for example code from my book, structured in this very way:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch25p868mandelbrotGCD/ch38p1106mandelbrotNoThreading/MyMandelbrotView.swift
In that example, look at how drawThatPuppy gets off the main thread to do the time-consuming calculations and then back on the main thread to do the drawing into the interface.

Better way to Trigger Asynchronous Callbacks in Objective-C

I am looking for a better way to do this, if possible.
I have an asynchronous callback that updates a local sqlite database. I set a flag in a singleton variable (archiveUpdateComplete) when the update completes. I sleep in a do-while until the flag gets set to true, then I hydrate my tableview. Would like to remove sleep()! Thanks for any suggestions.
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
- (void)viewDidLoad
{
dispatch_async(kBgQueue, ^{
//Hydrate word archive table view
do {
sleep(1.0);
} while ([sharedManager archiveUpdateComplete]==NO);
[self performSelectorOnMainThread:#selector(hydrateWordArchive) withObject:nil waitUntilDone:YES];
//Run custom activity indicator
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
}
If you need to poll
Polling/sleeping is rarely necessary or good. As an alternative:
You can attach an NSTimer to the main thread's run loop.
The selector the timer calls can test [sharedManager archiveUpdateComplete]
if YES is returned, then
invalidate the timer
call [MBProgressHUD hideHUDForView:self.view animated:YES];
If you don't need to poll
There are a few immediate alternatives. Which you choose depends on what knows about what:
If your manager knows who to message following completion, then the manager can simply message it. If that must occur on the main thread you can use -[NSObject performSelectorOnMainThread:withObject:waitUntilDone:] to forward to the main thread. You may also see this approach with delegates. In the case of a singleton, it doesn't make a lot of sense to take this route.
If your manager does not know who is interested in the change/completion, your manager can post a NSNotification after the task has finished (on the current thread or from the main thread).
Key Value Observing (KVO) is another option.
Perhaps I'm missing something, but why don't you just use a completion callback for this?
In other words, you change your computation to "think" in terms of nested blocks. The first async block (on some concurrent queue) does the work of updating the database, and when it's done it dispatches another async block (to the same concurrent queue) which hydrates the tableview. Finally, from that block you dispatch_async yet another block on the main queue which updates the UI, since that's the only bit that needs to execute on the main queue.
Rather than poll, in other words, you want to chain your async operations. See COMPLETION CALLBACKS section of the man page for dispatch_async().

CoreData: update in background and reading in main thread causes dead lock

I'm displaying a table with some data to the user. As soon as the view is presented, I'm making a web call to see if there's updated data (asynchronously). When the service call returns, I'd like to update my core data and the view.
Unfortunately I'm often getting dead locks because the view reads the same data as the service call writes. How can I solve this?
When I pause the simulator as soon as it's frozen, the waiting threads are waiting at the following places:
Background (updating) thread: (psynch_cvwait)
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
Main thread: (psynch_mutexwait)
performing a filteredArrayUsingPredicate
Thanks a lot!
-mergeChangesFromContextDidSaveNotification: will block the main thread. That call will lock both NSManagedObjectContext instances while it updates the main context with the changes being passed in.
This is generally considered unavoidable in pre-iOS 5 applications. You can minimize it by making more frequent, smaller, saves but it is still going to happen.
The only other option for pre-iOS 5 applications is to tell the main context to -reset: but that will require re-fetching everything -- another delay.
It looks like the main thread is trying to grab some low level lock that the background thread already has (or vice versa). Are you using #synchronized somewhere to provide mutex?
Anyway, is there any reason why your background thread needs to wait for -mergeChangesFromContextDidSaveNotification: to complete? If not, pass NO as the last parameter.
I had the same problem (lock on psynch_cvwait) when I was merging context changes (both ways) between the main and a background context (both using NSConfinementConcurrencyType). The problem was caused by subscribing to NSManagedObjectContextDidSaveNotification on a different queue from which it was sent:
[[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:mainContext
queue:bgQueue
usingBlock:^(NSNotification * _Nonnull note) {
// bgContext runs on bgQueue
[bgContext mergeChangesFromContextDidSaveNotification:note];
}]
As a result the block was never called, and both main and background queues hung on psynch_cvwait()
I fixed it by not blocking mainContext's queue:
[[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:mainContext
queue:nil
usingBlock:^(NSNotification * _Nonnull note) {
[bgQueue addOperationWithBlock:^{
[bgContext mergeChangesFromContextDidSaveNotification:note];
}];
}]
However, it doesn't seem to be a problem if I block the background queue when merging changes into the main context.