NSNotificationCenter has a maximum speed? - objective-c

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];

Related

Object keeps receiving notification

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];

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.

Cocoa app behaves diffirently with breakpoint on & off

Important update: I found out that most part of my question was based on a false premise (see my answer below). Notifications actually got to the receiver, they just got there too fast. (Although, it still doesn't explain why the behavior with breakpoint and without it was different.)
I'm developing the app that calculates the hashes of files given to it. The calculation takes place in SHHashComputer. It's an abstract class (well, intended to be abstract, as there are no abstract classes in Objective C) that takes the file path and creates an NSInvocationOperation. It, in turn, calls the method (void)computeAndSendHash, which uses the file path saved in the object to compute hash and sends it as notification. The actual computing takes place in (NSString*)computeHash method that child classes need to override.
Here's SHHashComputer.m:
- (NSString*)computeHash {
return [NSString stringWithFormat:#"unimplemented hash for file %#", self.path];
}
- (void)computeAndSendHash {
NSString *result = [self computeHash];
NSString *notificationName = [NSString stringWithFormat:#"%#%#",
gotResultNotification,
self.hashType];
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName
object:result];
self.operation = nil;
}
And here's SHMD5Computer.m (the child class of SHHashComputer):
- (NSString*)computeHash {
return #"MD5 test"; // it actually doesn't matter what it returns
}
I won't bother you with the receivers of notification. Let's just say that as long as I comment out the computeHash method in SHMD5Computer.m everything works just fine: the notification with text "unimplemented ..." is received & displayed in GUI. But if I don't — then it gets really interesting.
If I don't set up any breakpoints, the notification just never comes. However, if I set up a breakpoint at the declaration of computeHash in SHMD5Computer.h and then step over until the line 'self.operation = nil', and continue execution at that point, the notification gets to destination. If I don't stop there, the debugger suddenly switches to the state as if it isn't debugging anything, and the app freezes.
I don't think that 'WTF' is a good form for a question here, so let me put it this way: am I missing something? Are there errors in my code? What can cause this type of behavior in xcode? How can I fix this?
(If you'll want to get all my code to reproduce it, I'll gladly give it to you.)
More experiments:
If I continute execution exactly after stopping at breakpoint, the application encounters EXC_BAD_ACCESS error in the code that receives the notification, at the last line:
id newResult = [newResultNotification object];
if (newResult == nil)
[NSException raise:#"No object"
format:#"Expected object with notification!"];
else if (![newResult isKindOfClass:[NSString class]])
[NSException raise:#"Not NSString"
format:#"Expected NSString object!"];
else
self.result = (NSString*) newResult;
[self.textField setStringValue:self.result];
When I tried to reproduce the previous experiment, something even stranger happenned. In my debug setup, I have two hash computer objects: one SHMD5HashComputer (which we're talking about), and one SHHashComputer (which, of course, produces the "unimpemented" hash). In all previous experiments, as long as app didn't crash, the notification form SHHashComputer always successfully arrived. But in this case, both notifications didn't arrive, and the app didn't crash. (All the steps are exactly the same as in previous one).
As Josh Caswell pointer out in the comments, I wasn't using the notifications correctly. I should've sent the object itself as notification object, as described in documentation. I fixed that, and I'm getting exactly the same results. (Which means that I fixed it correctly, because sometimes the notifications work correctly, and also that it wasn't the problem).
More updates:
The notification that I'm sending should arrive at SHHashResultViewController. That's how I create it and register for notification:
- (id)initWithHashType:(NSString *)hashType {
self = [self initWithNibName:#"SHHashResultView" bundle:[NSBundle mainBundle]];
if (self) {
[self setHashType:hashType];
}
return self;
}
- (void)setHashType:(NSString *)hashType {
[self.label setStringValue:[NSString stringWithFormat:#"%#:", hashType]];
_hashType = hashType;
NSString *notificationName = [NSString stringWithFormat:#"%#%#",
gotResultNotification,
_hashType];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(gotResult:)
name:notificationName
object:nil];
}
Actually, the question was based on a false premise. I thought that notification never came through because I never saw the information displayed in the GUI; however, my error was in the code of controllers (not published there) which made possible the situation in which the GUI first got results of hash calculation and only after that got information about a new input — which resulted in deleting all the text and activating progress animation.

ios coredata updates not seen on different managed object contexts - data is different between contexts

We're having this issue where different threads see different data on the same records but with different managed object contexts (moc). Our app syncs in the background to a server API. All of the syncing is done on it's own thread and using it's own moc. However, we've discovered that when data gets updated on the main moc that change in data is not shown in the background moc. Any ideas what could be happening? Here's some more details: we're using grand central dispatch like so to put the sync operations on it's own thread: We've checked which queue things are running on and it all is happening on the queue expected.
- (void) executeSync; {
dispatch_async(backgroundQueue, ^(void) {
if([self isDebug])
NSLog(#"ICSyncController: executeSync queue:%# \n\n\n\n\n", [self queue]);
for(id <ICSyncControllerDelegate> delegate in delegates){
[delegate syncController:self];
}
if([ICAccountController sharedInstance].isLoggedIn == YES && shouldBeSyncing == YES) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 300ull * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
[self executeSync];
});
}
});
}
here's how we create the background moc and we've confirmed that it's created on the background queue.
- (NSManagedObjectContext*)backgroundObjectContext {
if (_backgroundObjectContext)
return _backgroundObjectContext;
_backgroundObjectContext = [[NSManagedObjectContext alloc] init];
[_backgroundObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
[_backgroundObjectContext setStalenessInterval:0.0];
return _backgroundObjectContext;
}
I should add that our background moc is requerying for data and those records returned from that action still have the old values for some fields. How does the background moc get the current data that was already saved by the main moc? I thought just by requerying I would get the current state of these records..
by requerying I mean the following:
The background MOC is executing another "query" to get "fresh" data after the records have been changed by the main moc, yet the data has old values - not the updated values seen in the main moc.
+ (NSArray *)dirtyObjectsInContext:(NSManagedObjectContext *)moc {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(memberships, $m, $m.category.name == %# AND $m.syncStatus > %d).#count > 0", MANAGED_CATEGORY_FAVORITES, ManagedObjectSynced];
return [self managedObjectsWithPredicate:predicate inContext:moc];
}
Your help is hugely appreciated as we've been trying to figure this out, or find a work around that doesn't include ditching our threads for days now.
That's how it's supposed to work -- indeed, an important role of the managed object context is to protect you from changes to the data made in other threads. Imagine the havoc that would result if you had a background thread modifying the same objects that the main thread was using without some sort of synchronization scheme.
Read Communicating Changes Between Contexts to learn how to merge changes from one context into another.
I use the following code to listen for changes on context 2, so that context 1 keeps up to date:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:context1
selector:#selector(contextTwoUpdated:)
name:NSManagedObjectContextDidSaveNotification
object:context2];
it causes this method to be called on context 1, and i invoke the merge method:
- (void)contextTwoUpdated:(NSNotification *)notification {
[context1 mergeChangesFromContextDidSaveNotification:notification];
}
a side effect of this is any NSFetchedResultsController that is attached to context1 will send a variety of messages to its delegate informing it of the changes,
i've never tried listening both ways while the user changes the object and you update them on the user from behind - i suspect you may have to manage merges if that's the case, since it's one-way (and all user driven) for me i assume all updates to be valid

NSFetchedResultsController not displaying changes from background thread

My app is using an NSFetchedResultsController tied to a Core Data store and it has worked well so far, but I am now trying to make the update code asynchronous and I am having issues. I have created an NSOperation sub-class to do my updates in and am successfully adding this new object to an NSOperationQueue. The updates code is executing as I expect it to and I have verified this through debug logs and by examining the SQLite store after it runs.
The problem is that after my background operation completes, the new (or updated) items do not appear in my UITableView. Based on my limited understanding, I believe that I need to notify the main managedObjectContext that changes have occurred so that they may be merged in. My notification is firing, nut no new items appear in the tableview. If I stop the app and restart it, the objects appear in the tableview, leading me to believe that they are being inserted to the core data store successfully but are not being merged into the managedObjectContext being used on the main thread.
I have included a sample of my operation's init, main and notification methods. Am I missing something important or maybe going about this in the wrong way? Any help would be greatly appreciated.
- (id)initWithDelegate:(AppDelegate *)theDelegate
{
if (!(self = [super init])) return nil;
delegate = theDelegate;
return self;
}
- (void)main
{
[self setUpdateContext:[self managedObjectContext]];
NSManagedObjectContext *mainMOC = [self newContextToMainStore];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:updateContext];
[self setMainContext:mainMOC];
// Create/update objects with mainContext.
NSError *error = nil;
if (![[self mainContext] save:&error]) {
DLog(#"Error saving event to CoreData store");
}
DLog(#"Core Data context saved");
}
- (void)contextDidSave:(NSNotification*)notification
{
DLog(#"Notification fired.");
SEL selector = #selector(mergeChangesFromContextDidSaveNotification:);
[[delegate managedObjectContext] performSelectorOnMainThread:selector
withObject:notification
waitUntilDone:YES];
}
While debugging, I examined the notification object that is being sent in contextDidSave: and it seems to contain all of the items that were added (excerpt below). This continues to make me think that the inserts/updates are happening correctly but somehow the merge is not being fired.
NSConcreteNotification 0x6b7b0b0 {name = NSManagingContextDidSaveChangesNotification; object = <NSManagedObjectContext: 0x5e8ab30>; userInfo = {
inserted = "{(\n <GCTeam: 0x6b77290> (entity: GCTeam; id: 0xdc5ea10 <x-coredata://F4091BAE-4B47-4F3A-A008-B6A35D7AB196/GCTeam/p1> ; data: {\n changed =
The method that receives your notification must indeed notify your context, you can try something like this, which is what I am doing in my application:
- (void)updateTable:(NSNotification *)saveNotification
{
if (fetchedResultsController == nil)
{
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
//Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
else
{
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
// Reload your table view data
[self.tableView reloadData];
}
}
Hope that helps.
Depending on the specifics of what you are doing, you may be going about this the wrong way.
For most cases, you can simply assign a delegate using NSFetchedResultsControllerDelegate. You provide an implementation for one of the methods specified in "respondingToChanges" depending on your needs, and then send the tableView a reloadData message.
The answer turned out to be unrelated to the posted code which ended up working as I expected. For reasons that I am still not entirely sure of, it had something to do with the first launch of the app. When I attempted to run my update operation on launches after the Core Data store was created, it worked as expected. I solved the problem by pre-loading a version of the sqlite database in the app so that it did not need to create an empty store on first launch. I wish I understood why this solved the problem, but I was planning on doing this either way. I am leaving this here in the hope that someone else may find it useful and not lose as much time as I did on this.
I've been running into a similar problem in the simulator. I was kicking off an update process when transitioning from the root table to the selected folder. The update process would update CoreData from a web server, save, then merge, but the data didn't show up. If I browsed back and forth a couple times it would show up eventually, and once it worked like clockwork (but I was never able to get that perfect run repeated). This gave me the idea that maybe it's a thread/event timing issue in the simulator, where the table is refreshing too fast or notifications just aren't being queued right or something along those lines. I decided to try running in Instruments to see if I could pinpoint the problem (all CoreData, CPU Monitor, Leaks, Allocations, Thread States, Dispatch, and a couple others). Every time I've done a "first run" with a blank slate since then it has worked perfectly. Maybe Instruments is slowing it down just enough?
Ultimately I need to test on the device to get an accurate test, and if the problem persists I will try your solution in the accepted answer (to create a base sql-lite db to load from).