I'm trying to do some tests with Apple's KVC but for some reason I can't get KVO to trigger when I change a value via KVC.
I have the following code:
#import <Foundation/Foundation.h>
#interface Character : NSObject
{
NSString *characterName;
NSInteger ownedClowCards;
}
#property (nonatomic, retain) NSString *characterName;
#property (nonatomic, assign) NSInteger ownedClowCards;
-(void)hasLostClowCard;
-(void)hasGainedClowCard;
#end
#implementation Character
#synthesize characterName;
#synthesize ownedClowCards;
-(void)hasLostClowCard
{
}
-(void)hasGainedClowCard
{
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"Change");
}
#end
int main()
{
Character *sakura;
Character *shaoran;
//---------------------------------------------------------------------
// Here begins the KVO section.
[sakura addObserver:sakura forKeyPath:#"ownedClowCards" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
//Create and give the properties some values with KVC...
sakura = [[Character alloc] init];
[sakura setValue:#"Sakura Kinomoto" forKey:#"characterName"];
[sakura setValue:[NSNumber numberWithInt:20] forKey:#"ownedClowCards"];
shaoran = [[Character alloc] init];
[shaoran setValue:#"Li Shaoran" forKey:#"characterName"];
[shaoran setValue:[NSNumber numberWithInt:21] forKey:#"ownedClowCards"];
//Done! Now we are going to fetch the values using KVC.
NSString *mainCharacter = [sakura valueForKey:#"characterName"];
NSNumber *mainCharCards = [sakura valueForKey:#"ownedClowCards"];
NSString *rival = [shaoran valueForKey:#"characterName"];
NSNumber *rivalCards = [shaoran valueForKey:#"ownedClowCards"];
NSLog(#"%# has %d Clow Cards", mainCharacter, [mainCharCards intValue]);
NSLog(#"%# has %d Clow Cards", rival, [rivalCards intValue]);
[sakura setValue:[NSNumber numberWithInt:22] forKey:#"ownedClowCards"];
}
Like you can see it's really, really basic code, so I'm ashamed I can't get this to work for whatever reason. Everything I'm trying to do is to get a notification when ownedClowCards changes. I am registering the observers. When I run my program, I expect to see the message "Changed" once when the program is done running. But it never does. Changed is never printed to my program so I assume observeValueForKeyPath:ofObject:change:context: is not getting called.
Any help?
[sakura addObserver:sakura forKeyPath:#"ownedClowCards" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
You are doing the above before initializing sakura. Of course setting nil to observe changes to nil does nothing.
You add your observer to an object that doesn't exist yet.
Character *sakura;
This simply declares the variable, but doesn't actually allocate or initialize it yet.
Try calling sakura = [[Character alloc] init]; before you register as an observer.
And by the way NSString properties usually use the copy flag and not retain. And a memory management flag (assign) doesn't make sense in the case of a primitive type (NSInteger).
Related
I have a NSString property, self.textFromTextVC, in a Viewcontroller and it's value becomes null in the IBAction method.
- (IBAction)buttonPressed:(id)sender
{
NSLog(#"text before alarm is created: %#", self.textFromTextVC);
}
The methods below are in the same '.m' file and they keep the value of the NSString property.
-(void)setPropertyTextToReceivedText:(NSString *)text
{
self.textFromTextVC = text;
NSLog(#"text received from text VC: %#", self.textFromTextVC);
[self doesStringKeepValue]; //I call this method to check and see if the NSString value
//was retained
}
-(void)doesStringKeepValue
{
NSLog(#"keep value: %#", self.textFromTextVC); //NSString value the same from the above
//method
}
Below is how I have declared the NSString property:
#property (nonatomic, copy) NSString *textFromTextVC;
Basically, I'm setting the self.textFromTextVC before the IBAction method is called and that is why I'm confused. I'm really not sure what is going on. I have ARC selected.
I'm hoping that I'm just making a simple mistake...help?
Thanks,
Below is the method in another viewcontroller where I called setPropertyTextToReceivedText:
#implementation TextViewController
#synthesize typedText;
- (IBAction)doneButton:(id)sender {
[self.typedText resignFirstResponder];
AlarmViewController *receiver = [[AlarmViewController alloc]init];
[receiver setPropertyTextToReceivedText:self.typedText.text];
//[self showAlert];
}
What your problem is receiver is different object than your VC which is shown (present/pushed).
AlarmViewController *receiver = [[AlarmViewController alloc]init];
[receiver setPropertyTextToReceivedText:self.typedText.text];
Change this:
NSLog(#"text before alarm is created: %#", self.textFromTextVC);
To this:
NSLog(#"%#: text before alarm is created: %#", self, self.textFromTextVC);
And it will probably show you that you are indeed looking at two different object instances of the same class.
I think you forgot:
#synthesize textFromTextVC;
I have this piece of code below and I'm trying to add Objects(String elements) to an array, problem is that every time I'm out its adding's method, it goes to nil, it doesn't retain the objects.
I know I'm doing wrong, even that I already tried lot of combinations and variations, even with my own constructor _MyArray etc etc, same result... it works, but not further...
Could you help me please?
#interface ArraysModel()
#property (nonatomic, retain) NSMutableArray *MyArray;
#end
#implementation ArraysModel
#synthesize MyArray;
-(void)AddObjectToTheList:(NSString *)object {
if(!MyArray) MyArray = [[NSMutableArray alloc] init];
[MyArray addObject:object];
NSLog(#"%#",self.MyArray);
NSLog(#"Object added %u",[self.MyArray count]);
}
-(NSMutableArray *)ObjectList {
return self.MyArray;
NSLog(#"%#",self.MyArray);
NSLog(#"Object added %u",[self.MyArray count]);
}
#end
The header is like this:
#interface ArraysModel : NSObject
-(void)AddObjectToTheList:(NSString *)object;
And here is my call from my ViewController:
- (IBAction)AddToTheList {
ArraysModel *MyObjectToAdd = [[ArraysModel alloc] init];
[MyObjectToAdd AddObjectToTheList:TextArea.text];
[self.view endEditing:YES];
Well, there's your problem -- you're alloc init'ing a new instance of ArraysModel, and therefore a new array with every call. You need to create a strong reference to your instance, and check for whether it exits, and only init if it doesn't.
In the .h:
#property (strong, nonatomic) ArraysModel *myObjectToAdd;
in the .m:
-(IBAction)AddToTheList {
if (! self.myObjectToAdd) {
self.myObjectToAdd = [[ArraysModel alloc] init];
}
[self.myObjectToAdd AddObjectToTheList:TextArea.text];
[self.view endEditing:YES]
}
I have one managed object with a one-to-many relationship to member class. When I add the observers for members, it worked. When one new member is added to the relationship, the observeValueForKeyPath will be invoked with the new object and change dictionary contains the new member object. However, observeValueForKeyPath will be triggered second time with all values nil and change dictionary new="NULL". What is the second trigger? I set a breakpoint, but not sure who made the trigger.
#interface FooObject : NSManagedObject {}
#property (nonatomic, strong) NSString *fooId;
#property (nonatomic, strong) NSSet* members;
#end
#implementation FooObject
#dynamic fooId;
#dynamic members;
- (NSMutableSet*)membersSet {
[self willAccessValueForKey:#"members"];
NSMutableSet *result = (NSMutableSet*)[self mutableSetValueForKey:#"members"];
[self didAccessValueForKey:#"members"];
return result;
}
- (void)registerObservers {
[self addObserver:self
forKeyPath:#"members"
options:NSKeyValueObservingOptionNew
context:nil];
}
- (void)unregisterObservers {
#try{
[self removeObserver:self forKeyPath:#"members"];
}#catch(id anException){
//do nothing, obviously it wasn't attached because an exception was thrown
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
id valueNewSet = [change objectForKey:NSKeyValueChangeNewKey];
if (![valueNewSet isKindOfClass:[NSSet class]]) {
// not a NSSet, it contains <null> value
NSLog(#"%#", change);
NSLog(#"%#", object);
}
if ([[change objectForKey:NSKeyValueChangeKindKey] intValue] == NSKeyValueChangeInsertion) {
// insert change is valid, process the changes
}
}
#end
Log output:
{
kind = 1;
new = "<null>";
}
<FooObject: 0xfa9cc60> (entity: FooObject; id: 0xfa9be00 <x-coredata://39DB31FD-6795-4FDE-B700-819AB22E5170/SHInterest/p6> ; data: {
fooId = nil;
members = nil;
})
EDIT 1
I set a breakpoint at NSLog(#"%#", change);
This is the stack trace but not really helpful to figure who makes this call.
main -> UIApplicationMain -> NSKeyValueNotifyObserver -> observeValueForKeyPath:ofObject:change:context
EDIT 2
Maybe this is still a bug?
http://www.cocoabuilder.com/archive/cocoa/182567-kvo-observevalueforkeypath-not-reflecting-changes.html
I am running into the same issue where the "implicit" assignment (and hence the notification of the KVO observer) apparently occurs only as part of deallocating MOs: I save a child MOC, then release it, then iOS releases its MOs. I assume that it then sets one-to-many relationships temporarily to NSNull in the process of deallocating related MOs (where delete rule cascade applies).
So next to change kinds for insertion and deletion my KVO observer now also accepts NSKeyValueChangeSetting and asserts change[NSKeyValueChangeNewKey] == NSNull.null. Call it a pragmatic solution.
Assume that I have a class with a readonly property on it.
//MyClass.h
#interface MyClass
#property (readonly) NSInteger MonitorMe;
#end
Now, let's assume the point of this property is to monitor changes of another property, within another object, and when the property is "observed" it returns a derived value by inspecting a value from the other, external object.
//MyClass.m
#implementation
#synthesize MonitorMe;
-(NSInteger) getMonitorMe
{
return globalStaticClass.OtherNSInteger;
}
... Inits and Methods ...
#end
Now, let's assume that some where I create an instance of the MyClass object, and I want to add a KVO observer on the MonitorMe property.
//AnotherClass.m
#implementation AnotherClass.m
#synthesize instanceOfMyClass;
-(id)init
{
...
instanceOfMyMethod = [MyClass init];
[MyClass addObserver: self
forKeyPath: #"MonitorMe"
options: NSKeyValuObservingOptionNew
context: nil];
...
}
My question is, since the MonitorMe property only monitors the changes of values in an external object, will the observer method execute when the value of globalStaticClass.OtherNSInteger changes? Also, if the answer is yes, how is this done?
If this works, it would seem like compiler voodoo to me.
Note
I don't think it makes a difference, but I am using ARC for this implementation and I'm compiling for an iOS device. I doubt there are compilation differences between OS X and iOS for this type of question but, if it matters, I have an iOS project that requires such an implementation outlined above.
Also, the example outlined above is a very basic setup of my actual needs. It could be argued that I could/should add an observation to the globalStaticClass.OtherNSInteger value instead of the readonly property, MonitorMe. In my actual circumstance that answer is not sufficient because my readonly property is much more complex than my example.
will the observer method execute when the value of globalStaticClass.OtherNSInteger changes?
No, but you can make that happen, via +keyPathsForValuesAffectingMonitorMe (or the more generic +keyPathsForValuesAffectingValueForKey:, if the "globalStaticClass" is actually a property of MyClass. See "Registering Dependent Keys" in the KVO Guide.
Here's a quick mockup:
#import <Foundation/Foundation.h>
#interface Monitored : NSObject
#property NSInteger otherInteger;
#end
#implementation Monitored
#synthesize otherInteger;
#end
#interface Container : NSObject
#property (readonly) NSInteger monitorMe;
#property (strong) Monitored * theMonitored;
- (void)changeMonitoredInteger;
#end
#implementation Container
#synthesize theMonitored;
+ (NSSet *)keyPathsForValuesAffectingMonitorMe {
return [NSSet setWithObject:#"theMonitored.otherInteger"];
}
- (id) init {
self = [super init];
if( !self ) return nil;
theMonitored = [[Monitored alloc] init];
[theMonitored setOtherInteger:25];
return self;
}
- (NSInteger)monitorMe
{
return [[self theMonitored] otherInteger];
}
- (void)changeMonitoredInteger {
[[self theMonitored] setOtherInteger:arc4random()];
}
#end
#interface Observer : NSObject
#end
#implementation Observer
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"Observing change in: %# %#", keyPath, object);
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
Observer * o = [[Observer alloc] init];
Container * c = [[Container alloc] init];
[c addObserver:o
forKeyPath:#"monitorMe"
options:NSKeyValueObservingOptionNew
context:NULL];
[c changeMonitoredInteger];
[c changeMonitoredInteger];
}
return 0;
}
P.S. Cocoa style notes: properties/variables should have lowercase initial letters, and (this is actually more important now because of ARC) don't name accessor methods to start with "get" -- that has a specific meaning in Cocoa involving passing in a buffer and getting data back by reference.
What's wrong with this? The NSMutableString returns (null).
.h:
NSMutableString *aMutableString;
...
#property (assign) NSMutableString *aMutableString;
.m:
#synthesize aMutableString;
- (void)aMethod {
[self setAMutableString:[[NSMutableString alloc] initWithString:#"message: "]];
if (someCondition) {
[[self aMutableString] appendString:#"woohoo"];
}
}
- (void)anotherMethod {
NSLog(#"%#", [self aMutableString]);
[[self aMutableString] release];
}
First of all, your code has a couple of problems. First, you should define your aMutableString #property as retain, not assign. assign is generally for primitive, non-object types, like ints, etc., and for some special cases of objects. You appear to want to take ownership of aMutableString in such a way that it persists after the event loop returns. In your posted code, you end up accomplishing that because of how you incorrectly set the aMutableString in the following line:
[self setAMutableString:[[NSMutableString alloc] initWithString:#"message: "]];
By creating an NSMutableString with alloc/init, you're creating a potential memory leak situation, though in your situation, it actually makes up for your defining the property as assign rather than retain.
Your second -anotherMethod is also potentially dangerous in that:
1) it releases an instance variable you defined as assign
2) after releasing it, it doesn't set it to nil. If you try to access that instance variable elsewhere in that class at a later point in time, you will likely get a crash because the pointer is no longer valid, if the instance variable has been dealloced.
So, the code should most likely look something like this:
.h
NSMutableString *aMutableString;
...
#property (retain) NSMutableString *aMutableString;
.m:
#synthesize aMutableString;
- (void)dealloc {
[aMutableString release];
[super dealloc];
}
- (void)aMethod {
[self setAMutableString:[NSMutableString stringWithString:#"message: "]];
if (someCondition) {
[aMutableString appendString:#"woohoo"];
}
}
- (void)anotherMethod {
NSLog(#"%#", aMutableString);
// the following is potentially unsafe!
// [[self aMutableString] release];
// it should be one of the following:
[aMutableString release]; aMutableString = nil;
// or
// [self setAMutableString:nil];
}
That said, without more information, it's a little hard to say what the problem is. I assume you mean the NSLog() call is printing (null)? If so, that means that aMutableString is still nil. Are you calling -aMethod before calling -anotherMethod?
If you want to make sure that aMutableString is initialized to an empty string, you could override -init:
- (id)init {
if ((self = [super init])) {
aMutableString = [[NSMutableString alloc] init];
}
return self;
}
Make sure you have #synthesize aMutableString; in your .m file