I'm getting a weird zombie object issue when I'm trying to merge my changes from a background NSOperation:
(controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:]: message sent to deallocated instance)
I have a ViewController that pushes other controllers on a navController stack in my AppDelegate in the didSelectRowForIndexPath like so:
ABCViewController *myVC = [[ABCViewController alloc] initWithNibName:#"ABCViewController" bundle:nil];
ABCEvent *selectedEvent = [_fetchedResultsController objectAtIndexPath:indexPath];
[myVC setManagedObjectContext:[self managedObjectContext]];
[myVC setTitle:#"Title"];
[myVC setEvent:selectedEvent];
ABCAppDelegate *appDelegate = (ABCAppDelegate *)[[UIApplication sharedApplication] delegate];
[[appDelegate navController] pushViewController:myVC animated:YES];
[myVC release];
Then in my ViewDidLoad for ABCViewContoller I'm creating an operation queue, and adding my background operation to it:
_operationQueue = [[NSOperationQueue alloc] init];
As well as hooking up a notification so I can merge the changes:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
All standard stuff I believe. The issue is if I select the row in the main VC, then go back to it, and hit the row again, I get that: message sent to deallocated instance message. Now, if I turn off the notification for NSManagedObjectContextDidSaveNotification, then I don't get the error, so that's definitely the culprit. My dealloc in my second controller is like this:
- (void)dealloc
{
[super dealloc];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_operationQueue release];
}
So I'm unhooking that class as an observer. In my mind it shouldn't be trying to merge changes on any unallocated instances. I believe somehow though it's trying to merge changes back in on the first instance of my controller when I touch the row again, even though I stopped subscribing to that notification.
I'm pretty stumped on this. Any help would be much appreciated.
Are you using a fetchedResultsControllerDelegate at all?
If you are, make sure to also set that to nil when the view disappears or unloads, otherwise it will continue to receive messages about changes in your data, regardless of removing the observer.
One quick correction that should help is your [super dealloc] should always be the last statement in your dealloc.
Also, since you are doing CoreData operations on a background thread (via NSOperationQueue), make sure you are creating your NSManagedObjectContext for your background thread object while in the background thread. Do not create it on one thread and assign it to an object that will run in a different thread, or you will have all sorts of issues.
When you get your NSManagedObjectContextDidSaveNotification notification, you need to do the merge on the same thread that the context you are merging into belongs on. More than likely this will be your main context in (it sounds like) your app delegate, which means it is on the main thread.
Related
Hi I have a FriendsViewController where I display my friends records fetched from coreData. I have another View Controller AddFriendViewController Which is presented by FriendsViewController to add a new friend the and it saves the the Context in it. I am listening to this notification of changes on the shared MOC in my FriendsViewController.
[[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:appdelegate.context queue:nil
usingBlock:^(NSNotification * _Nonnull note) {
NSLog(#"Re-Fetch Whole Friends Array from core data and Sort it with UILocalizedIndexedCollation and reloadData into table");
}];
In AddFriendsViewController just create a friend object and i
Friend *friend= [NSEntityDescription
insertNewObjectForEntityForName:#"Friend"
inManagedObjectContext:appdelegate.context];
friend.name=nameTextfield.text;
[appdelegate.context save:&error];
[self.navigationController popViewControllerAnimated:YES];
Now when I perform save on the context from a AddFriendViewController the above block in FriendsViewController is triggered couple of times instead of one time which cause more processing because re-fetching whole data from core data.I cannot use Fetched Results Controller because I am using UILocalizedIndexedCollation to sort my array into section. So my question is why it is being called twice or sometimes even thrice? Or is there any alternative for this ?
Only you know when you want the notification observer to be active.
However, two common paradigms are:
If you want to be notified anytime during the life of the view controller, then you register the observer in viewDidLoad and remove the observer in dealloc.
If you want to be notified anytime the view is active, you register the observer in viewWillAppear and remove in viewWillDisappear.
EDIT
I used this statement to remove all entries [[NSNotificationCenter
defaultCenter]removeObserver:self]; And it was still showing same
behaviour. Then I used addObserver: selector: name: object: method
which worked. But Why the other one was not removed ? – Asadullah Ali
That's because you added a block-based observer, which returns an observer object. You remove the object it returned to you. You really should read the documentation of each method that you use.
If you use the block-observer method, the add/remove will look like this.
id observer = [[NSNotificationCenter defaultCenter]
addObserverForName:SomeNotification
object:objectBeingObserved
queue:nil
usingBlock:^(NSNotification *note) {
// Do something
}];
[[NSNotificationCenter defaultCenter] removeObserver:observer];
If you use the selector-observer method, you need to remove the observer that you provide to the add call.
[[NSNotificationCenter defaultCenter]
addObserver:someObject
selector:#selector(notificationHandler:)
name:SomeNotification
object:objectBeingObserved];
[[NSNotificationCenter defaultCenter]
removeObserver:someObject
name:SomeNotification
object:objectBeingObserved];
First, I would strongly suggest using a NSFetchedResultsController instead of building your own observer.
Second, sounds like you are adding the observer several times. You should add it in the -viewDidLoad and remove it in -dealloc.
Again, a NSFetchedResultsController is a better solution. You will have better performance and avoid refetching like you are doing now.
You must get rid of the observer (as others already have stated here). The easiest way would be to use a "one-time observer" which removes itself when activated. In your example code this would be something like:
id __block observer;
observer = [[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:[PubStore sharedStore].managedObjectContext queue:nil
usingBlock:^(NSNotification * _Nonnull notification) {
[[NSNotificationCenter defaultCenter] removeObserver:observer];
NSLog(#"Re-Fetch Whole Friends Array from core data and Sort it with UILocalizedIndexedCollation and reloadData into table");
}];
Note that you must store the observer in a __block variable to be able to use it inside the block executed when the observer is triggered.
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.
Within an App I make use of several viewcontrollers. On one viewcontroller an observer is initialized as follows:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MyNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethod:) name:#"MyNotification" object:nil];
Even when removing the NSNotification before initializing the number of executions of myMethod: is being summed up by the amount of repeated views on the respective viewcontroller.
Why does this happen and how can I avoid myMethod: being called more then once.
Note: I made sure by using breakpoints that I did not made mistakes on calling postNotification multiple times.
Edit: This is how my postNotification looks like
NSArray * objects = [NSArray arrayWithObjects:[NSNumber numberWithInt:number],someText, nil];
NSArray * keys = [NSArray arrayWithObjects:#"Number",#"Text", nil];
NSDictionary * userInfo = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self userInfo:userInfo];
edit: even after moving my subscribing to viewwillappear: I get the same result. myMethod: is called multiple times. (number of times i reload the viewcontroller).
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MyNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethod:) name:#"MyNotification" object:nil];
}
edit: something seems wrong with my lifecycle. ViewDidUnload and dealloc are not getting called, however viewdiddisappear is getting called.
The way I push my Viewcontroller to the stack is as follows where parent is a tableview subclass (on clicking the row this viewcontroller is initiated:
detailScreen * screen = [[detailScreen alloc] initWithContentID:ID andFullContentArray:fullContentIndex andParent:parent];
[self.navigationController pushViewController:screen animated:YES];
Solution:
Moving removal of nsnotification to viewdiddisappear did the trick. Thanks for guidance!
Based on this description, a likely cause is that your viewcontrollers are over-retained and not released when you think they are. This is quite common even with ARC if things are over-retained. So, you think that you have only one instance of a given viewcontroller active, whereas you actually have several live instances, and they all listen to the notifications.
If I was in this situation, I would put a breakpoint in the viewcontroller’s dealloc method and make sure it is deallocated correctly, if that’s the intended design of your app.
In which methods did you register the observers?
Apple recommends that observers should be registered in viewWillAppear: and unregistered in viewWillDissapear:
Are you sure that you don't register the observer twice?
Ran into this issue in an application running swift. The application got the notification once when first launched. the notification increases the number of times you go into the background and come back. i.e
app launches one - add observer gets gets called once in view will appear or view did load - notification is called once
app goes into background and comes back, add observer gets called again in view will appear or view did load. notification gets called twice.
the number increases the number of times you go into background and come back.
code in view will disappear will make no difference as the view is still in the window stack and has not been removed from it.
solution:
observe application will resign active in your view controller:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationWillResign:", name: UIApplicationWillResignActiveNotification, object: nil)
func applicationWillResign(notification : NSNotification) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
this will make sure that your view controller will remove the observer for the notification when the view goes into background.
it is quite possible you are subscribing to the notifications
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self userInfo:userInfo];
before self gets initialized. And trying to unsubscribe 'self' which isn't really subscribed to, and you will get all global myNotification notifications.
If your view was hooked up in IB, use -awakeFromNib: as the starting point to register for notifications
It is possible that the class with the observer is, quite appropriately, instantiated multiple times. When you are debugging it will kinda look like the notification is being posted multiple times. But if you inspect self you might see that each time is for a different instance.
This could easily be the case if your app uses a tab bar and the observer is in a base class of which your view controllers are subclasses.
-(void)someBackgroundTask {
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[context setPersistentStoreCoordinator:[self coordinator]];
// ...
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:#selector(handleSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:context];
[context save:&error];
// safe?
[notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:context];
// ...
}
// meanwhile on the main thread...
-(void)handleSaveNotification:(NSNotification *)notification {
[[self context] mergeChangesFromContextDidSaveNotification:notification];
}
Is is safe to remove the observer so soon after the call to save:?
As long as you've received the notification you want, it's not too soon. But there are other problems with that code.
It doesn't make any sense to add an observer, trigger the notification, and then remove the observer. You might as well call the handleSaveNotification method directly instead of bothering with notifications. It would have the same effect with less work.
The reason for this is that notifications are delivered synchronously on the thread they were posted on. So if someBackgroundTask is actually running on a background thread or queue, handleSaveNotification will also run in the background. Using notifications like this doesn't make you cross threads. To do the merge on the main thread, you have a few options, including:
Register for the notification using addObserverForName:object:queue:usingBlock:. With that method you can tell the notification center which queue to use. Make sure to save a reference to the object this method returns-- you'll need it later to remove the observer.
Call the merge method directly, but in that method use dispatch_async or performSelectorOnMainThread:withObject:waitUntilDone: to move the merge over to the main thread.
This is my first attempt with NSNotification, tried several tutorials but somehow it's not working.
Basically I am sending a dictionary to class B which is popup subview (UIViewController) and testing to whether is has been received.
Could anyone please tell me what am I doing wrong?
Class A
- (IBAction)selectRoutine:(id)sender {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
NSDictionary *dictionary = [NSDictionary dictionaryWithObject:#"Right"
forKey:#"Orientation"];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"PassData"
object:nil
userInfo:dictionary];
createExercisePopupViewController* popupController = [storyboard instantiateViewControllerWithIdentifier:#"createExercisePopupView"];
//Tell the operating system the CreateRoutine view controller
//is becoming a child:
[self addChildViewController:popupController];
//add the target frame to self's view:
[self.view addSubview:popupController.view];
//Tell the operating system the view controller has moved:
[popupController didMoveToParentViewController:self];
}
Class B
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(receiveData:)
name:#"PassData"
object:nil];
}
- (void)receiveData:(NSNotification *)notification {
NSLog(#"Data received: %#", [[notification userInfo] valueForKey:#"Orientation"]);
}
If it hasn't registered to receive that notification yet - it will never receive it. Notifications don't persist. If there isn't a registered listener, the posted notification will be lost.
Specific to your problem, the receiver hasn't started observing before the notification is sent so the notification just gets lost.
More generally: What you're doing wrong is using notifications for this use case. It's fine if you're just playing around and experimenting but the kind of relationship you're modelling here is best actioned by retaining a reference to the view and calling methods on it directly. It's usually best if experimentation is realistic of the situation in which it would actually be used.
You should be aware of 3 basic communication mechanisms and when to use them:
Notifications
Use them to notify other unknown objects that something has happened. Use them when you don't know who wants to respond to the event. Use them when multiple different objects want to respond to the event.
Usually the observer is registered for most of their lifetime. It's important to ensure the observer removes itself from NSNotificationCenter before it is destroyed.
Delegation
Use delegation when one object wants to get data from an unknown source or pass responsibility for some decision to an unknown 'advisor'.
Methods
Use direct calls when you know who the destination object is, what they need and when they need it.