How to force NSUndoManager prepareWithInvocationTarget to retain it's arguments? - ios

NSUndoManager method prepareWithInvocationTarget does not retain arguments. There is no links to this in Apple docs, but I've checked with profiler, and I'm pretty sure - it does not. This means if you are going to delete an object and be prepared for undo - you should retain it yourself (like assign it to some trash array and remove the original link). Those kind of fake removes creates a lot of unnecessary fuss, especially when you need to get rid of the old undos.
However, NSInvocation can retain arguments by calling retainArguments method. Since NSUndoManager uses NSInvocation for prepareWithInvocationTarget it might be the way to pass retainArguments to NSUndoManager.
The question is - how to do it?

It's right there in the Undo Architecture guide:
An NSUndoManager object does not retain the targets of undo
operations. The client—the object performing undo operations—typically
owns the undo manager, so if the undo manager in turn retained its
target this would frequently create a retain cycle.
So No, it doesn't - for the reasons given in the docs.
You'll have to manage live cycles on your own. How entirely depends on and is completely specific to your apps model.. I guess the general question boils down to:
How do we know when the NSUndoManager will 'pop' an action from the undo stack (so we can safely delete our context data we're keeping around for the undo operation).
Unfortunately there's no (documented) notification in the NSUndoManager Class Reference that's sent e.g. when removeAllActionsWithTarget: is invoked on an undo manager instance.
Almost seems like there's no real alternative to keep that additional information around forever (or manually limit the levels of undo and adjust the storage for the undo context data accordingly).

While the answer that was given is correct with respect to the target, the answer with respect to the arguments is: There's no need to retain them, because the undo manager does it. See Does NSUndoManager retain its arguments?

Related

Write to Core Data context without notification from observer

I have a NSManagedObjectContextDidSave observer that modifies a record directly after it has been saved. As imagined I am having an infinite loop issue where modifying the newly saved record will trigger another notification to the observer.
My thought would be to track the ID of the Core Data record and when it comes back around I simply ignore it and remove the ID from the array. This would work but I am worried about collisions, it seems highly unlikely but still possible that collisions would occur which would cause a record that collides to miss the modifications from the observer.
What is the common knowledge solution here?
The simple answer seems to be to use NSManagedObjectContextWillSave instead of NSManagedObjectContextDidSave, so that you can make the change before saving and therefore avoid a loop. Unless there's some compelling reason you didn't mention, that's what I'd do.

KVO notifications after mergeChangesFromContextDidSaveNotification

I'm using KVO to observe changes on a NSManagedObject. The NSManagedObject I'm observing is part of a NSManagedObject context that is on the main queue.
When I update this object in a background (private queue concurrency type) context and then merge the saved changes to my main queue context (in mergeChangesFromContextDidSaveNotification), KVO notifications fire as expected.
However, I expected that the notifications would only fire for key paths that actually changed and not for all keypaths of the NSManagedObject. I'm receiving KVO notifications for every keypath of my object even though they didn't change.
Is this by design or am I doing something wrong?
Can't see anything in the apple docs....
It is undocumented but observed behaviour on both OS X and iOS that a save counts as a change to the entire NSManagedObject not just differing elements. You can find grumblings about various consequences of that for bindings and the like around this site, on openradar.appspot.com, etc. That the problem also manifests with apparently-spurious KVO firings is completely unsurprising.
Simplest way to handle the problem (well, simplest after 'just redisplay everything on a save' which I find a fine first pass option until someone complains) is to listen for the generic save notification, then call -changedValues on each updated object to pick out the ones you're interested in firing specific updates for.
If that's hopelessly inefficient for your use case, you could make custom accessors (mogenerator is a big help with this) for your properties that collect on the editing thread flags for changes to all the properties you're interested in; and dispatch that as a notification after saving.
Let us for instance say that we have a professional sports team app that is updated constantly with JSON feeds parsed in the background. All display-affecting attributes of the various team, player, game, etc. NSManagedObjects have custom accessors that set flags in a structure of { playerStatsChanged, teamStatsChanged, leagueRankingsChanged, yadayadayadaChanged } corresponding to what pages in the app will need redisplay once the current fetch-and-parse thread completes. Then once it's saved, it fires off a generic 'update these screens' notification with that flag setting structure. You're probably coalescing individual change path notifications into some higher level 'update this screen' type logic somewhere in any case, right? Well, at the property setter level is pretty much the lowest overhead point you can do that at, for most reasonable use cases. Certainly for any recurring fetched update design such as our sports team apps here.
You can override the automatic change notifications with manual notifications for only the keys of your choice. Check the detailed documentation here.

Check retain count in dealloc method

Recently I was facing an issue where I was navigating from a screen A to screen B. when I was coming back from screen B to screen A, the Live Bytes in the application were not returning to the initial value. After further investigation I found out that I was retaining some global objects in some methods which were called more than once. So I had to fix the calling mechanism of the method.
I fixed the issue, but I was thinking about one alternate solution. What if I simply used a for loop in dealloc which runs depending on the value of retain count. I think it is not advisable to use such approach, but what is the exact problem in this approach if I am sure that the objects are not accessed from anywhere outside the file.
Thanks in advance.
What if I simply used a for loop in dealloc which runs depending on the value of retain count.
I wouldn't be surprised if Xcode detects code like that and energizes the aluminum case of your MacBook Pro with several amps.
I think it is not advisable to use such approach, but what is the
exact problem in this approach if I am sure that the objects are not
accessed from anywhere outside the file.
You're right -- not advisable. There are at least two problems:
It completely breaks the memory management paradigm of Objective-C. You really can't be sure that no other object has retained one of your objects. Just one example: you don't know in your -dealloc method whether any of the objects to which your ivars refer might have been autoreleased.
It's the wrong fix. Doing what you propose doesn't fix bugs in your code, it only covers them up. Your objects should correctly manage the objects that they use, and not worry about what other objects may or may not have retained. If you follow that simple formula, you don't have to worry about whether objects are accessed from "outside the file" or not -- everything just works.
Not only should you not use -retainCount to run the number of retains down to 0, you shouldn't look at -retainCount at all.
Retain count is not for you to count on. There are some internal implementations which increase/decrease the retain count without you know it so using it is not advisable.
You should use the xcode instruments for finding memory leaks which will lead you to places in your code where objects are retained and not released.
or you can just enable ARC and let it manage the memory for you.

Updating a flag when an attribute changes

As part of my syncing solution, I use a sync status for all objects of a certain class. Whenever specific (not all) attributes of that object change, I want to update the status.
I am considering four approaches:
Manually setting the status in code wherever I change
something that needs to synced. This is the most obvious, but also
the most laborious and error-prone (I'll need to remember to also add the sync status update any time I add new functionality).
Track it using a core data notification (e.g. willSave or
NSManagedObjectContextObjectsDidChangeNotification). This seems the most appropriate at first glance -
I simply sign up for the notifications in my AppDelegate and update
the status each time. But will it be possible to examine the changes
and only update the sync status when an attribute I care about is
updated? Also, won't setting
the sync_status itself also fires this notification, leading me into an endless loop? How would I address this?
Custom setters on the attributes I care about. I have had
trouble trying to get this working before, and eventually decided
to try to leave the standard core data getters/setters alone. But I
would delve back in if it is the best fit.
KVO. I've not used this pattern before, but it might be easiest to
just sign up for notifications of attribute changes for those I care
about and set the flag there. But where would I do this? I need
to monitor every object of the class, so would it be possible to start observing an
attribute's KVO notifications in that same object's awakeFromInsert?
I.e., whenever an object is created, immediately have that same
object listen for changes to attributeA and set it's own sync_status
when it fires?
Which of these approaches will serve me best? Perhaps I am missing some other ideas?
Manually setting the status code
Probably a bad idea, for exactly the reason you describe. You'll need to do this in all kinds of cases. You might not always be the developer on the app. One day you or someone else will forget it. Even if you don't, you get extra code all over the place that could be centralized.
Track it using a core data notification [...] Also, won't setting the sync_status itself also fires this notification, leading me into an endless loop?
It depends how you do it. Listening for NSManagedObjectContextDidSaveNotification could work, if you use a secondary NSManagedObjectContext. That way you can set the sync flag, save changes, and avoid looping because you're saving on a different context that you're not observing.
Using NSManagedObjectContextObjectsDidChangeNotification could also work. That will be posted when object properties are changed but a save is not actually in progress yet. Inspect the userInfo dictionary to see if anything you care about has changed, and if so, set your sync_status flag. Setting the flag would trigger a new notification, but it will be one you don't care about, so you break the loop. Using a separate context would also prevent looping here.
Custom setters on the attributes I care about.
Definitely workable, though depending on how many attributes you care about, you could end up with a lot of accessors just to update sync status. Of the four you mention, this is the one I would use.
A related but simpler approach would be to override willSave on your managed object classes. That will get called just before a save. Implement it to
Look through [self changedValues] for attributes that trigger syncing.
Set the sync flag if you find any of them.
This way you only have one custom method per entity, no matter how many attributes end up triggering the sync flag.
KVO
Should work, but is probably less intuitive to get working right than custom setters.
When I had to do something like this, I put the sync information outside of my data store. I'd listen for NSManagedObjectContextDidSaveNotification, and in the observer method I would:
Look through userInfo to see what had changed
Get the NSManagedObjectID for each object that would need to sync
Convert the object IDs to NSStrings and add those to a list saved in a separate file.
On a successful sync I'd clear out the object ID list.
The thinking was mainly that a sync flag is more metadata than actual model data, so I kept it out of the model. If you prefer to keep it in the model, I'd go with overriding willSave.

Core Data - undoing changes after saving a context

I've recently noticed this strange thing about undo mechanism in Core Data and it's bothering me ever since.
A quote from NSManagedObjectContext documentation for -undo method:
Sends an undo message to the receiver’s undo manager, asking it to reverse the latest uncommitted changes applied to objects in the object graph.
To reverse the latest uncommitted changes, sounds simple, right?
However, it's not what is actually happening! Even if I save the context with changes on my managed object, the following -undo call will still successfully reverse the changes. Isn't it against the thing stated in the docs?
Perhaps I'm doing something wrong? I can post my little testing code if needed. I'm really confused.
You should be confused. The Core Data documentation is a hot mess. They use a lot of words like "uncommitted" in arguably inappropriate ways. They seem to mean objects whose properties isFaulted is equal to NO when they say "uncommitted".
The Core Data Programming guide goes into more detail:
Change and Undo Management
A context keeps strong references to managed objects that have pending
changes (insertions, deletions, or updates) until the context is sent
a save:, reset , rollback, or dealloc message, or the appropriate
number of undos to undo the change.
The undo manager associated with a context keeps strong references to
any changed managed objects. By default, in OS X the context’s undo
manager keeps an unlimited undo/redo stack. To limit your
application's memory footprint, you should make sure that you scrub
(using removeAllActions) the context’s undo stack as and when
appropriate. Unless you keep a strong reference to a context’s undo
manager, it is deallocated with its context.
The wording/vocabulary in the documentation is not clear or consistent. I believe the intended usage is that the you should call removeAllActions on the context's undoManager property when appropriate for your application to avoid unlimited memory growth.

Resources