In relation to this question I was wondering if there is any generally accepted logic regarding when to use NSNotification, with an observer in your main thread, vs using GCD to dispatch work from a background thread to the main thread?
It seems that with a notification-observer setup you have to remember to tear down the observer when your view unloads but then you reliably ignore the notification, where as dispatching a job to the main thread may result in a block being executed when the view has been unloaded.
As such, it seems to me that notifications should provide improved app stability. I'm assuming that the dispatch option provides better performance from what I've read of GCD?
UPDATE:
I'm aware that notifications and dispatch can work happily together and in some cases, should be used together. I'm trying to find out if there are specific cases where one should/shouldn't be used.
An example case: Why would I select the main thread to fire a notification from a dispatched block rather than just dispatching the receiving function on the main queue? (Obviously there would be some changes to the receiving function in the two cases, but the end result would seem to be the same).
The NSNotificationCenter and gcd & dispatch_get_main_queue() serve very different purposes. I don't thing "vs" is truly applicable.
NSNotificationCenter provides a way of de-coupling disparate parts of your application. For example the kReachabilityChangedNotification in Apple's Reachability sample code is posted to the notification center when the system network status changes. And in turn you can ask the notification center to call your selector/invocation so you can respond to such an event.(Think Air Raid Siren)
gcd on the other hand provides a quick way of assigning work to be done on a queue specified by you. It lets you tell the system the points at which your code can be dissected and processed separately to take advantage of multiple-threads and of multi-core CPUs.
Generally (almost always) the notifications are observed on the thread on which they are posted. With the notable exception of one piece of API...
The one piece of API where these to concepts intersect is NSNotificationCenter's:
addObserverForName:object:queue:usingBlock:
This is essentially a convenient method for ensuring that a given notification is observed on a given thread. Although the "usingBlock" parameter gives away that behind the scenes it's using gcd.
Here is an example of its usage. Suppose somewhere in my code there is an NSTimer calling this method every second:
-(void)timerTimedOut:(NSTimer *)timer{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// Ha! Gotcha this is on a background thread.
[[NSNotificationCenter defaultCenter] postNotificationName:backgroundColorIsGettingBoringNotification object:nil];
});
}
I want to use the backgroundColorIsGettingBoringNotification as a signal to me to change my view controller's view's background color. But it's posted on a background thread. Well I can use the afore mentioned API to observe that only on the main thread. Note viewDidLoad in the following code:
#implementation NoWayWillMyBackgroundBeBoringViewController {
id _observer;
}
-(void)observeHeyNotification:(NSNotification *)note{
static NSArray *rainbow = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
rainbow = #[[UIColor redColor], [UIColor orangeColor], [UIColor yellowColor], [UIColor greenColor], [UIColor blueColor], [UIColor purpleColor]];
});
NSInteger colorIndex = [rainbow indexOfObject:self.view.backgroundColor];
colorIndex++;
if (colorIndex == rainbow.count) colorIndex = 0;
self.view.backgroundColor = [rainbow objectAtIndex:colorIndex];
}
- (void)viewDidLoad{
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
__weak PNE_ViewController *weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:backgroundColorIsGettingBoringNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note){
[weakSelf observeHeyNotification:note];
}];
}
-(void)viewDidUnload{
[super viewDidUnload];
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
The primary advantage of this API seems to be that your observing block will be called during the postNotification... call. If you used the standard API and implemented observeHeyNotification: like the following there would be no guarantee how long it would be before your dispatch block was executed:
-(void)observeHeyNotification:(NSNotification *)note{
dispatch_async(dispatch_get_main_queue(), ^{
// Same stuff here...
});
}
Of course in this example you could simply not post the notification on a background thread, but this might come in handy if you are using a framework which makes no guarantees about on which thread it will post notifications.
Related
In my iOS application, I am posting a NSNotification and catching it in one of my UIView in main thread. I want to pass extra information along with the notification. I was using userInfo dictionary of NSNotification for that.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotifyValueComputedFromJS" object:self userInfo:#{#"notificationKey":key,#"notificationValue":value,#"notificationColor":color,#"notificationTimeStamp":time}];
key, value, color and time are local variables which contains the value I need to pass. In UIView I am adding observer for this notification and I am using notification.userInfo to get these data
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"NotifyValueComputedFromJS" object:nil];
-(void)receiveNotification:(NSNotification *)notification
{
if ([notification.userInfo valueForKey:#"notificationKey"]!=nil && [[notification.userInfo valueForKey:#"notificationKey"] isEqualToString:self.notificationKey] && [notification.userInfo valueForKey:#"notificationValue"]!=nil) {
[self updateLabelWithValue:[notification.userInfo valueForKey:#"notificationValue"]];
}
}
The frequency in which this notification is posted is 4 times in one second. I am doing some animations also in main thread. The problem I am facing here is my UI is lagging. UI will respond to scroll events or touch events with huge delay(I have faced a delay of even 1 to 2 seconds). After some research I came to know that NSDictionary is bulky and will cause lag if used in main thread. Is there any other way I can pass my data through NSNotification?
I have tried out another way. I have created a custom NSObject class to save the data I want and I am passing it as the object parameter of postNotification method.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotifyValueComputedFromJS" object:customDataObject userInfo:nil];
Here customDataObject is an instance of my custom NSObject class. I know the parameter is meant to be the sender of notification(usually it will be self). Is it a wrong approach if I am sending a custom object as parameter?
As BobDave mentioned, the key is to send the notification on some thread other than the main UI thread. This can be accomplished with dispatch_async, or with a queue.
The typical pattern for this behavior is sender:
-(void)sendDataToObserver {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotifyValueComputedFromJS" object:customDataObject userInfo:userInfo:#{#"notificationKey":key,#"notificationValue":value,#"notificationColor":color,#"notificationTimeStamp":time}];
});
}
And receiver (NOTE: weak self because retain cycles):
-(void)addObserver {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"NotifyValueComputedFromJS" object:nil];
}
-(void)receiveNotification:(NSNotification *)notification {
if ([notification.userInfo valueForKey:#"notificationKey"]!=nil && [[notification.userInfo valueForKey:#"notificationKey"] isEqualToString:self.notificationKey] && [notification.userInfo valueForKey:#"notificationValue"]!=nil) {
__weak typeof (self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf updateLabelWithValue:[notification.userInfo valueForKey:#"notificationValue"]];
});
}
}
Maybe you could use - addObserverForName:object:queue:usingBlock:
and use a non-main queue to execute the block in order to reduce the lag. Also, shouldn't the observer be added in a UIViewController, not a UIView?
in my app I am implementing my internet network with global dispatch queue and gcd.
I want to set network indicator visible while there is network activity.
here is my network block - >
{
[[UIApplication sharedApplication]setNetworkActivityIndicatorVisible:YES];
send sync http request
[[UIApplication sharedApplication]setNetworkActivityIndicatorVisible:NO];
}
my questions :
I want to check if there is a block that doesn't executed yet! before hiding network indicator. How could I implement that !
does calling setNetworkActivityIndicatorVisible from another thread, safe because i see that NetworkActivityIndicatorVisible is nonatomic.
#DavidBemerguy's approach is a good start, but you'd typically want to implement it with dispatch_group_notify to hide your indicator. That said, IMO, you don't need GCD here. You just need a NetworkIndicatorController.
Create an object (the controller) that listens to notifications like DidStartNetworkActivity and DidStopNetworkActivity. Post notifications when you start or stop. Inside the controller, keep a count and when it hits 0, hide the indicator. Something like this (totally untested, just typing here, and I've been writing exclusively in Swift for the last few days, so forgive any missing semicolons):
.h:
extern NSString * const DidStartNetworkActivityNotification;
extern NSString * const DidStopNetworkActivityNotification;
#interface NetworkIndicatorController
- (void) start;
- (void) stop;
#end
.m
#interface NetworkIndicatorController ()
#property (nonatomic, readwrite, assign) NSInteger count;
#end
#implementation NetworkIndicatorController
- (void)start {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self name:DidStartNetworkActivityNotification selector:#selector(didStart:) object:nil];
[nc addObserver:self name:DidStopNetworkActivityNotification selector:#selector(didStop:) object:nil];
self.count = 0;
}
- (void)stop {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self name:DidStartNetworkActivityNotification object:nil];
[nc removeObserver:self name:DidStopNetworkActivityNotification object:nil];
}
- (void)didStart:(NSNotification *)note {
dispatch_async(dispatch_get_main_queue(), {
self.count = self.count + 1;
[[UIApplication sharedApplication]setNetworkActivityIndicatorVisible:YES];
});
}
- (void)didStop:(NSNotification *)note {
dispatch_async(dispatch_get_main_queue(), {
self.count = self.count - 1;
if (self.count <= 0) {
[[UIApplication sharedApplication]setNetworkActivityIndicatorVisible:NO];
}
});
}
You could get similar stuff with dispatch_group, but I think this is simpler. The problem with the dispatch group approach is keeping track of when you do and don't want to call dispatch_notify. I'm sure the final code isn't that hard, but it's trickier to think about all the possible race conditions.
You could also just directly call -startNetworkActivity and -stopNetworkActivity directly on an instance of this object that you pass around rather than using notifications.
One possible approach is to create a group of tasks, then waiting for them to finish. Between the start and finish you can update your activity indicator. Obviously you'll need some object that will retain reference to the group. Check this code, based on apple documentation:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // Retain this queue somewhere accessible from the places you want to dispatch
dispatch_group_t group = dispatch_group_create(); // Retain this group somewhere accessible from the places you want to dispatch
// Add a task to the group
dispatch_group_async(group, queue, ^{
// Some asynchronous work
});
// Do some other work while the tasks execute.
disptach_sync(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication]setNetworkActivityIndicatorVisible:YES];
}
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Release the group when it is no longer needed.
disptach_sync(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication]setNetworkActivityIndicatorVisible:NO];
}
dispatch_release(group);
Remember that you can have a singleton object that you dispatch your blocks and and keeps track of your wait.
I'm trying to create a class cluster as subclass of UIViewController to accomplish some points:
1. Different behavior of the ViewController depending on actual iOS version
2. iOS version checks don't clutter up the code
3. Caller doesn't need to care
So far I got the classes MyViewController, MyViewController_iOS7 and MyViewController_Legacy.
To create instances I call the method myViewControllerWithStuff:(StuffClass*)stuff which is implemented like:
+(id)myViewControllerWithStuff:(StuffClass*)stuff
{
if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1)
{
return [[MyViewController_iOS7 alloc] initWithStuff:stuff];
}
else
{
return [[MyViewController_Legacy alloc] initWithStuff:stuff];
}
}
The caller uses myViewControllerWithStuff:. After that the so created view controller gets pushed onto a UINavigationController's navigation stack.
This nearly works as intended with one big downside: ARC doesn't dealloc the instance of MyViewController_xxx when it gets popped from the navigation stack. Doesn't matter which iOS version.
What am I missing?
UPDATE: -initWithStuff:
-(id)initWithStuff:(StuffClass*)stuff
{
if (self = [super init])
{
self.stuff = stuff;
}
return self;
}
This method is also implemented in MyViewController. The differences kick in later (e.g. viewDidLoad:).
First of all: Thanks for all your help, comments, answers, suggestions...
Of course there was another strong reference to the MyViewController-object. But it wasn't that obvious, because it was not a property or instance variable.
In viewDidLoad I did the following:
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self saveState];
}];
This should prevent data loss in case the user sends the app to the background. Of course the block captures the needed parts of its environment. In this case it captured self. The block keeps self alive until it (the block) gets destroyed, which is the case when e.g. [[NSNotificationCenter defaultCenter] removeObserver:self]; gets called. But, bad luck, this call is placed in the dealloc method of MyViewController which won't get called as long as the block exists...
The fix is as follows:
__weak MyViewController *weakSelf = self;
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[weakSelf saveState];
}];
Now the block captures weakSelf. This way it can't keep the MyViewController-object alive and everything deallocs and works just fine.
I came across intriguing behaviour when using NSManagedObjectContext's performBlock: with notification center.
From the main UI thread I trigger asynchronous data download (using NSURLConnection's connectionWithRequest:). When data arrive the following delegate method is called:
- (void)downloadCompleted:(NSData *)data
{
NSArray *new_data = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
self.backgroundObjectContext = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.backgroundObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
[self.backgroundObjectContext performBlockAndWait:^{
[self saveToCoreData:new_data];
}];
}
The savetoCoreData: method is simply saving new data to the background context:
- (void)saveToCoreData:(NSArray*)questionsArray
{
for (NSDictionary *questionDictionaryObject in questionsArray) {
Question *newQuestion = [NSEntityDescription
insertNewObjectForEntityForName:#"Question"
inManagedObjectContext:self.backgroundObjectContext];
newQuestion.content = [questionDictionaryObject objectForKey:#"content"];
}
NSError *savingError = nil;
[self.backgroundObjectContext save:&savingError];
}
In the view controller, in viewDidLoad I add observer to the notification center:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextChanged:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
And then in the contexChanged: I merge the background context with the main context so that my NSFetchedResultsController's delegate methods are called where my view can be updated:
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == self.managedObjectContext) return;
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
It all seems to work well, but there is one thing that bothers me. When in downloadCompleted: method I use performBlock: instead of performBlockAndWait: the notification seems to be delayed. It takes noticeable (around 5s) amount of time from the moment the background thread does save: till the moment NSFetchedResultsController calls its delegate. When I use performBlockAndWait: I do not observe any visible delay - the delegate is called as fast as if I called saveToCoreData: inside _dispatch_async_.
Does anyone saw that before and know if this is normal or am I abusing something?
As pointed out by Dan in one of the comments, the merge operation should happen on the main thread. This can be easily observed by changing the contextChanged: method to do the following:
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == self.managedObjectContext) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(contextChanged:)
withObject:notification
waitUntilDone:YES];
return;
}
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
With this change, both performBlock: and performBlockAndWait: are working.
As long as this explains to some extent why the problems were occurring in the first place, I still do not understand why performBlock: and performBlockAndWait: perform differently from the threading perspective. Apple documentation says:
performBlock: and performBlockAndWait: ensure the block operations are executed on the queue specified for the context. The performBlock: method returns immediately and the context executes the block methods on its own thread. With the performBlockAndWait: method, the context still executes the block methods on its own thread, but the method doesn’t return until the block is executed.
This indicates, that if the true root cause of the problem described in the question is that merging is happening in the background thread, then I should observe identical behaviour regardless of which method I am calling: performBlock: and performBlockAndWait: - both are executing in a sperate thread.
As a side note, since the NSURLConnection calls the delegate method on the same thread that started the asynchronous load operation - main thread in my case - using background context seems to be not necessary at all. NSURLConnections deliver its events in the run loop anyway.
I have 3 screens on my app.First is login. Second is search and third is process the task.
On login i retrieve data from a web service. It returns data in XML format. So the data is considerably large. So i am doing that task on a background thread like this to stop Mainthread freezing up on me:
-(BOOL)loginEmp
{
.....some computation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
[self getAllCustomerValues];
});
}
-(void)getAllCustomerValues
{
....more computation.Bring the data,parse it and save it to CoreData DB.
//notification - EDIT
NSNotification *notification =[NSNotification notificationWithName:#"reloadRequest"
object:self];
[[NSNotificationCenter defaultCenter] postNotification : notification];
}
//EDIT
//SearchScreenVC.m
- (void)viewDidLoad
{
....some computation
[self.customerActIndicator startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(stopActivityIndicator)
name:#"reloadRequest"
object:nil];
}
- (void)stopActivityIndicator
{
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
So on condition that login was successful, i move to screen 2. But the background thread is still in process( i know because i have logs logging values) . I want an activity indicator showing up here (2nd screen)telling user to wait before he starts searching. So how do i do it?How can i make my activity indicator listen/wait for background thread. Please let me know if you need more info.Thanks
EDIT: so I edited accordingly but the notification never gets called. I put a notification at the end of getAllCustomerValues and in viewDidLoad of SearchScreen i used it. That notification on 2nd screen to stop animating never gets called. What is the mistake i am doing.?Thanks
EDIT 2: So it finally hits the method. I dont know what made it to hit that method. I put a break point. I wrote to stop animating but it wouldn't. I wrote hidesWhenStoppped and hidden both to YES. But it still keeps animating.How do i get it to stop?
Ok, if it is not the main thread, put the following in and that should fix it.
- (void)stopActivityIndicator
{
if(![NSThread isMainThread]){
[self performSelectorOnMainThread:#selector(stopActivityIndicator) withObject:nil waitUntilDone:NO];
return;
}
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
Could you put your background operation into a separate class and then set a delegate on it so you can alert the delegate once the operation has completed?
I havent tried this, its just an idea :)
You could use a delegate pointing to your view controller & a method in your view controller like:
- (void) updateProgress:(NSNumber*)percentageComplete {
}
And then in the background thread:
float percentComplete = 0.5; // for example
NSNumber *percentComplete = [NSNumber numberWithFloat:percentComplete];
[delegate performSelectorOnMainThread:#selector(updateProgress:) withObject:percentageComplete waitUntilDone:NO];