Observing changes in the properties of an NSManagedObject: how to avoid looping? - cocoa

In my application, I observe the properties of a managed object. A change may lead to adjustments in some of its other properties, so the managed object itself receives a message of a changed property. These changes happen through bindings that are set up in the Interface Builder.
I have the following method in the implementation of the managed object:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ( !processingChange )
{
processingChange = YES;
*** DO STUFF TO THIS MANAGED OBJECT'S PROPERTIES ***
[self.managedObjectContext processPendingChanges];
processingChange = NO;
return;
}
}
The processingChange boolean is there to avoid an endless "notification loop", but it is not working as I expect (plus it looks like a real dirty hack).
There must be another way to do this. Any suggestions?

use MOMs' setPrimitiveValue:forKey: it doesnt generate KVOs

I think no need to send the notification "by hand", take a look: https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/10000177i
The observeValueForKeyPath:ofObject:change:context: method is
automatically invoked when the value of an observed property is
changed in a KVO-compliant manner, or if a key upon which it depends
is changed.
Maybe this is even the mistake?

Related

Handling NSMenuDelegate menuWillOpen for changing targets

There are lots of related answers about using menuWillOpen. They all explain that one needs to set the menu's delegate first.
This is easy when I have just one target, like a Preferences window or the main application.
But what if I have a document based app, and I need to have the active document handle menuWillOpen? Then the delegate isn't a constant any more.
What's the proper way to handle this? Do I have to set the delegate to a single object (like the AppDelegate) and then forward the call to the active view controller (but how is that done correctly)? Or is there some other elegant way?
I came up with this code which appears to work:
// This is in my AppDelegate class, and the NSMenu's delegate points to it:
- (void)menuWillOpen:(NSMenu *)menu {
// Forward to active document controller
NSWindow *mainWindow = [NSApplication sharedApplication].mainWindow;
NSResponder *r = mainWindow.firstResponder;
while (r) {
if ([r respondsToSelector:_cmd]) {
[(id<NSMenuDelegate>)r menuWillOpen:menu];
return;
}
r = r.nextResponder;
}
}
It assumes that a controller down the responder chain implements menuWillOpen:

How to properly use Cocos2d's update/tick method to trigger an event, only once?

I keep running into this issue wherein I'd like to trigger an event (void) during a scheduled update or tick method - but only trigger it once. The problem is that it gets triggered every time update/tick gets called (each frame). Depending on what method is being called, this slows down the game and occasionally crashes (e.g. addChild already added). I've used a BOOL (e.g. eventTriggered) before to try to handle this situation but am wondering if that is the only and/or best way?
If you're using cocos2d 2.0 just use:
[self scheduleOnce:#selector(yourMethod:) delay:3.0f];
In all other cases simply unschedule the scheduled selector:
-(void) yourScheduledMethodThatShouldOnlyRunOnce:(ccTime)delta
{
[self unschedule:_cmd];
// do stuff once
}
If it's a custom method you need to have some condition that fires the method call, for example:
-(void) update:(ccTime)delta
{
if (runThisNowButOnlyOnce)
{
runThisNowButOnlyOnce = NO;
[self runThisNowButOnlyOnceMethod];
}
}
You just need to figure out when and where to set runThisNowButOnlyOnce to YES. Also don't forget to add it as an ivar to the #interface.

How to manage multiple windows in Cocoa apps with Interface Builder

I have this application with 3 classes: AppController, Profile, ProfileBuilder. I also need 3 windows: one for each class. I tried keeping all 3 as subclasses of NSObject and applying initWithNibName to an NSWindowController class WindowController variable but when I tried outputting some values on each window it wouldn't work, and also the window resulted as null using NSLog. I was wondering what was the best way to manage multiple windows, perhaps all from a same class like an AppWindowsController involving as least as possible specific code in the other classes, and keeping, if possible, the other classes as subclasses of NSObject and not NSWindowController. So if there is, maybe a way to control the behavior of the windows remotely, adding as least as possible code inside the specific classes, just to keep them as clear as possible and uniquely focused on their content. Thanks, hope I made myself clear, I'm actually pretty new to the Cocoa framework.
You should be able to load the nib files with your windows in an init method for your different classes. For example, in Profile, you could do something like this:
-(id)init {
if (self = [super init]) {
NSArray *array;
BOOL success = [[NSBundle mainBundle] loadNibNamed:#"ProfileWindow" owner: self topLevelObjects:&array];
if (success) {
for (id obj in array) {
if ([obj isKindOfClass:[NSWindow class]]) {
self.profileWindow = obj;
}
}
[self.profileWindow makeKeyAndOrderFront:self];
}
}
return self;
}
profileWindow is a property (typed as strong). In the xib file, I set the File's Owner to Profile.
I just like to improve the solution of rdelmar.
You don't need to iterate over the array to find the NSWindow class.
If you define profileWindow as an outlet and connect it in the IB, the call
[[NSBundle mainBundle] loadNibNamed:#"ProfileWindow" owner:self topLevelObjects:&array];
will assign the window object to your outlet, the array stuff is not required.
The key here is the owner object which act as interface. In the IB you can define the class type of the owner and if so, see its outlets.

NSKeyedArchiver: distinguishing between different instances of the same class

I'm implementing support for Lion's "Resume" feature in my OS X app.
I have a custom subclass of NSViewController in which I implemented the method
encodeRestorableStateWithCoder: as:
#implementation MyClass (Restoration)
-(void)encodeRestorableStateWithCoder:(NSCoder*)coder {
[coder encodeObject:_dataMember forKey:#"object_key"]; // I get the warning below when this line is executed for the second time
}
- (void)restoreStateWithCoder:(NSCoder *)coder {
_dataMember = [coder decodeObjectForKey:#"object_key"];
}
#end
However, since I have multiple instances of MyClass, different values are saved into the same key ("object_key") and I get the following warning from Cocoa:
NSKeyedArchiver warning: replacing existing value for key
'object_key'; probable duplication of encoding keys in class hierarchy
What is the best practice to overcome this problem?
Edit: I found here that each instance automatically has its own namespace to avoid collisions, so the problem might be in the way I'm manually calling encodeRestorableStateWithCoder to different instances with the same NSCoder object without telling it that these are different instances. However, I still can't figure out how to do that properly.
Thanks in advance!
To overcome this problem, it is possible to create a new NSMutableData where each of which is written by a separate (new) NSKeyArchiver, and store them all in an array which is stored in the original NSCoder object.
Here is an example for encoding the restorable state of subitems. The decoding part can be straight-forward given this code.
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[super encodeRestorableStateWithCoder:coder];
// Encode subitems states:
NSArray* subitems = self.items;
NSMutableArray* states = [NSMutableArray arrayWithCapacity: subitems.count];
for (SubItemClass* item in subitems)
{
NSMutableData* state = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:state];
[item encodeRestorableStateWithCoder:archiver];
[archiver finishEncoding];
[states addObject:state];
}
[coder encodeObject:states forKey:#"subitems"];
}

Getting an exception when using KVO

I am implementing MKMapView based application. In that I am using an observer when we tap on a pin. the observer is code is follows,
[annView  addObserver:self
forKeyPath:#"selected"
options:NSKeyValueObservingOptionNew
context:#"ANSELECTED"];
It is working as expected, but some time it is getting exception 'EXC_BAD_ACCESS'. My log is as follows and it is showing me a leaking memory. Do I need to release the server?. If I ? then where should I release this?
An instance 0x1b21f0 of class MKAnnotationView is being deallocated
while key value observers are still registered with it. Observation
info is being leaked, and may even become mistakenly attached to
some other object. Set a breakpoint on NSKVODeallocateBreak to stop
here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0x11e5f0> (
<NSKeyValueObservance 0x1b1da0: Observer: 0x120f70, Key path: selected, Options: <New: YES, Old: NO, Prior: NO> Context: 0x2b588, Property: 0x1acaa0>
It is working as excepted, but some time it is getting exception 'EXC_BAD_ACCESS'. My log is as follows and it is showing me a leaking memory. …
An instance 0x1b21f0 of class MKAnnotationView is being deallocated while key value observers are still registered with it.
That's the opposite of a leak. It is being deallocated; a leak is when an object will never be deallocated.
The problem is that it's being deallocated while something else is still observing it. Anything that's still observing this object may also send it other messages later; when it does, those messages will go to a dead object (causing the crash you saw, which happened after that message) or to a different object.
If the object that is observing the MKAnnotationView is owning it and releasing it, it needs to remove itself as an observer before releasing it. If it does not own it, it probably should.
You have to stop observing the annotation view before you release it:
[annView removeObserver:self forKeyPath:#"selected"];
I know that it's quite old but I see that this code is used a lot on stackoverflow and in other repositories, and here is a solution to the problem.
You should create an NSMutableArray ivar in your view controller class in order to store a reference to your annotations view:
MyMapViewController <MKMapViewDelegate> {
NSMutableArray *annot;
}
Initialize it in your viewDidLoad and in your - (MKAnnotationView *) mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>) annotation
you should add the MKAnnotationView to the mutable array itself right before the annView addObserver code:
if(nil == annView) {
annView = [[MyAnnotationView alloc] initWithAnnotation:myAnnotation reuseIdentifier:identifier];
[annot addObject:annView];
}
[annView addObserver:self
forKeyPath:#"selected"
options:NSKeyValueObservingOptionNew
context:(__bridge void *)(GMAP_ANNOTATION_SELECTED)];
Then, in your viewDidDisappear method you can iterate the array and remove manually all the observers:
//remove observers from annotation
for (MKAnnotationView *anView in annot){
[anView removeObserver:self forKeyPath:#"selected"];
}
[annot removeAllObjects];
[super viewDidDisappear:animated];

Resources