Object keeps receiving notification - objective-c

I have a Book object that listens to a notifications. When I'm trying to remove the book from the view I have this piece of code:
BookItem *book = [books objectAtIndex:bookIndex];
[book removeFromSuperview];
[books removeObject:book];
book = nil;
After I'm done removing books I send a "rearrange" notification and I'm getting an error in the BookItem object, where I'm accessing nulls ...
What could be the problem? how can I stop listening to notifications after I'm removing the object?

You need to stop observing:
[[NSNotificationCenter defaultCenter] removeObserver:book];

Related

How to observe notifications from a different application?

I'd like to get notified when a certain application triggers an event. I'm not an Objective-C developer nor do I know the OS X APIs—so I hope this question is not too basic.
My goal is to write the meta info of the currently playing song to a log file. For iTunes, I got this working with the following Objective-C line:
[[NSDistributedNotificationCenter defaultCenter]
addObserver: myObserver selector: #selector(observeNotification:)
name: #"com.apple.iTunes.playerInfo" object:nil];
However, I also need this for AirServer (which is an software AirPlay receiver). Unfortunately, the following doesn't work—the observer never gets called:
[[NSDistributedNotificationCenter defaultCenter]
addObserver: myObserver selector: #selector(observeNotification:)
name: #"com.pratikkumar.airserver-mac" object:nil];
So apparently, AirServer does not send these type of notifications. However, there's a notification in the Notification Center when a new song starts playing.
My next step would be to periodically check for new notifications in the OS X Notification Center (as described here: https://stackoverflow.com/a/25930769/1387396). This is not too clean though—so my questions is: is there another option in this particular case?
Catalina silently changed the addObserver behavior - you can no longer use a nil value for the name to observe all notifications - you have to pass in a name. This makes discovery of event names more difficult.
Firstly, you have to understand that while the NSDistributedNotificationCenter has the word Notification in it; it's not related. From the About Local Notifications and Remote Notifications, it does state:
Note: Remote notifications and local notifications are not related to broadcast notifications (NSNotificationCenter) or key-value observing notifications.
So, at that point, I'm going to answer from the perspective of NSDistributedNotificationCenter, and not about Remote/Local notifications - you already have a potential solution in the answer you linked for observing the DB file that contains a record of the notifications in that way.
This example code will not work on Catalina (10.15) because of API behavior changes
The first thing you need to do is listen for the correct notification. Creating a simple application that listens for all events; e.g. using:
NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(notificationEvent:)
name:nil
object:nil];
-(void)notificationEvent:(NSNotification *)notif {
NSLog(#"%#", notif);
}
It indicates that the notifications are:
__CFNotification 0x6100000464b0 {name = com.airserverapp.AudioDidStart; object = com.pratikkumar.airserver-mac; userInfo = {
ip = "2002:d55e:dbb2:1:10e0:1bfb:4e81:b481";
remote = YES;
}}
__CFNotification 0x618000042790 {name = com.airserverapp.AudioDidStop; object = com.pratikkumar.airserver-mac; userInfo = {
ip = "2002:d55e:dbb2:1:10e0:1bfb:4e81:b481";
remote = YES;
}}
This indicates that the name parameter in the addObserver call should be com.airserverapp.AudioDidStart and com.airserverapp.AudioDidStop.
You can use code like that to determine all notifications, which will allow you to add the relevant observers when you want the particular observer. This is probably the easiest way to observe notifications of this type.

NSNotificationCenter has a maximum speed?

Using NSNotificationCenter , it worked well till i have started to send very fast messages to other class.
What is fast ? its about 30-40 notifications in second. and i dont get even 1 of them .
is there other way to do that ?
should i update a global instead ?
//post data out .
- (void)post:(NSString*)string
{
NSLog(#"done"); //the log is printing
NSDictionary *userInfo = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:#"connector"
object:string
userInfo:userInfo];
}
I know the observer is good cause same code worked before .
Thanks a lot .
If you're sending 30-40 notifications a second you should really think about your implementation again.
Heres some alternatives:
Creating your own delegates for callbacks
Using a block
The problem with NSNotificationCentre is that it sends the message to every observer - this can get slow and is generally used for updating views on state change (Login/Logout).
It looks like you setting the wrong object, Which should be the object that posts the notification (or nil). What I think you should be doing adding the string to userInfo:
NSDictionary *userInfo = #{#"somekey" : string };
[[NSNotificationCenter defaultCenter] postNotificationName:#"connector"
object:self
userInfo:userInfo];

NSManagedObjectContext working in background

I have problem with merging changes between two context. One of them is working in background(additional context) as WebServices reading and writing data to database. While I was trying save any changes in main context, application gone stuck and nothing happend. Context from web services(singleton) I create as I showed below. When I try save main context I send notification to merge changes with WebServices context but it does not work correctly. Whats wrong? It happend when background is reading data and main context try to save something i database.
managedObjectContext = [NSManagedObjectContext new];
NSPersistentStoreCoordinator *store = [ [theDelegate managedObjectContext] persistentStoreCoordinator];
[managedObjectContext setPersistentStoreCoordinator:store];
[managedObjectContext setStalenessInterval:0.0];
Before save of Main Context I Use:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:self.managedObjectContext];
where I merge changes
[[[WebServie instance] managedObjectContext] mergeChangesFromContextDidSaveNotification:notification]
It should working but I have no idea whats going wrong, WebServices are working on background, and DataBaseManager is in main thread
Thats code of my merge context is it wrong?
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserverForName:NSManagedObjectContextDidSaveNotification
object: self.managedObjectContext queue:nil
usingBlock:^(NSNotification *notification)
{
NSLog(#"merge");
[[[WebServiceManager instance] managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}];
NSError *error;
NSLog(#"error");
if (![self.managedObjectContext save:&error])
{
NSLog(#"error :%#", error);// Update to handle any error appropriately.
}
NSLog(#"after error");
[dnc removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:self.managedObjectContext];
You need to do the merging on the queue of the context that's supposed to do the merge. Not on the queue (or thread) that it's being sent on.
If you are trying to keep two context synchronized, you need to listen to the NSManagedObjectContextDidSaveNotification for both context, and merge the notification of the first context to the second and viceversa. So, let's say you have your context on your applicationDelegate, and you have a context on your web singleton. You would create a method on your applicationDelegate to be fired when the singleton context saves. On that method, you would take the notification from the singleton object and merge it with the main thread context. On your singleton, you would listen to the NSManagedObjectContextDidSaveNotification from the main thread and merge it with the singleton's context.

FetchedResultsController doesn't see the changes in managedObjectContext after data import

I'm working on the data import part in my app, and to make the UI more reliable, i followed this Marcus Zarra article
http://www.cimgf.com/2011/08/22/importing-and-displaying-large-data-sets-in-core-data/
The idea is that you make the import in a separate context in the background tread(i use GCD for that), and your fetchedResultsController's context merges the changes by observing the NSManagedObjectContextDidSaveNotification.
The issue i get is very strange to me - my fetchedResultsController doesn't get those changes itsef and doesn't reload the TableView when the new data comes.
But if i fire the following method, which makes the fetch and reloads the table - it gets it all there.
- (void)updateUI
{
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
[self.tableView reloadData];
}
So now i call that method when i get the NSManagedObjectContextDidSaveNotification to make it work, but it looks strange and nasty to me.
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == [self managedObjectContext]) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(contextChanged:) withObject:notification waitUntilDone:NO];
return;
}
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
//TODO:Make it work as it should - merge, without updateUI
[self updateUI];//!!!Want to get rid of this!
}
Why can it be like this?
Here is the code that is responsible for parsing the data and adding the Observer.
- (void)parseWordsFromServer:(NSNotification *)notification
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) , ^{
NSDictionary *listInJSON = [notification userInfo];
wordsNumbers = [[listInJSON valueForKey:#"words"]mutableCopy];
if ([wordsNumbers count])
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
//New Context for the new thread
NSManagedObjectContext *backContext = [[AppDelegate sharedAppDelegate]backManagedObjectContext];
//Get all the words we already have on this device
NSArray *wordsWeHave = [Word wordsWithNumbers:wordsNumbers inManagedContext:backContext];
//Add them to this list
for (Word *word in wordsWeHave)
[[List listWithID:[currentList listID] inManagedObjectContext:backContext]addWordsObject:word];
[backContext save:nil];!//Save the context - get the notification
}
});
}
EDIT
I use the NSFetchedResutsControllerDelegate, indeed, how else could i pretend my tableview to be updated if i didn't?
UPDATE Decided just to move to Parent - Child paradigm
The problem has been discussed many times, like NSFetchedResultsController doesn't show updates from a different context, and it's quite difficult to understand what is going on but I have few notes.
First, you are violating a simple rule: you need to have a managed object context per thread (Concurrency with Core Data Section).
Create a separate managed object context for each thread and share a
single persistent store coordinator.
So, inside your custom thread access the main context, grab its persistent coordinator and set it to the new context.
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setPersistentStoreCoordinator:persistentStoreCoordinatorGrabbedFromAppDelegate];
Second, you don't need to register
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
within the new thread. Just register for it within the class that create the new thread (or in the app delegate).
Finally, if you are not using a NSFetchedResutsControllerDelegate, use it. It allows to get rid of reloading data table. When the context changes, the delegate responds to changes: edit, remove, add.
Starting from iOS 5, you could just use new Core Data API and make your life easier with new confinement mechanism.
Edit
From #mros comment.
Multi-Context CoreData
It may help you understand a little bit more about the advantages of
using a parent-child core data model. I particularly like the bit
about using a private queue context to handle the persistent store.
Make sure to read down through the whole thing because the beginning
shows how not to do it.
Hope that helps.

Giving notification to another class with NSNotificationCenter

So my goal is to deliver a notification to another class with using NSNotificationCenter, I also want to pass object with the notification to the other class, how should I do this?
You must first register a notification name
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(startLocating:) name:#"ForceUpdateLocation" object:nil]; // don't forget the ":"
And then post a notification with a dictionary of parameters
[[NSNotificationCenter defaultCenter] postNotificationName:#"ForceUpdateLocation" object:self userInfo:[NSDictionary dictionaryWithObject:#"1,2,3,4,5" forKey:#"categories_ids"]];
and the method will be
- (void)startLocating:(NSNotification *)notification {
NSDictionary *dict = [notification userInfo];
}
Just call any method for posting notifications as described here, for instance :
to post a notification :
-(void)postNotificationName:(NSString *)notificationName
object:(id)notificationSender
userInfo:(NSDictionary *)userInfo;
where userInfo is a dictionary containing useful objects.
On the other side to register for notifications :
-(void)addObserver:(id)notificationObserver
selector:(SEL)notificationSelector
name:(NSString *)notificationName
object:(id)notificationSender;
You could also check Apple's Notification Programming Topics.