KVO: +keyPathsForValuesAffecting<Key> doesn't work with (subclass of) NSObjectController - objective-c

I have a KVO-able class (call it Observee), which affectedValue dynamic property is affected by affectingValue property. The dependency between the properties is defined by implementing +keyPathsForValuesAffectingAffectedValue method.
Setting a value to affectingValue notifies that affectedValue has changed as I expected, unless Ovservee is a subclass of NSObjectController. Full example follows:
#interface Observee : NSObject // or NSObjectController
#property (readonly, strong, nonatomic) id affectedValue;
#property (strong, nonatomic) id affectingValue;
#property (strong, nonatomic) NSArrayController *arrayController;
#end
#implementation Observee
#dynamic affectedValue;
- (id)affectedValue { return nil; }
+ (NSSet *)keyPathsForValuesAffectingAffectedValue {
NSLog(#"keyPathsForValuesAffectingAffectedValue called");
return [NSSet setWithObject:#"affectingValue"];
}
#end
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (strong, nonatomic) Observee *observee;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
self.observee = [[Observee alloc] init];
[self.observee addObserver:self
forKeyPath:#"affectedValue"
options:NSKeyValueObservingOptionNew
context:NULL];
NSLog(#"setting value to affectingValue");
self.observee.affectingValue = #42;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
NSLog(#"affected key path = %#", keyPath);
}
#end
The example works fine and outputs as the following when Observee derives NSObject:
keyPathsForValuesAffectingAffectedValue called
setting value to affectingValue
affected key path = affectedValue
but when Observee derives NSObjectController:
keyPathsForValuesAffectingAffectedValue called
setting value to affectingValue
(note that "affected key path = affectedValue" is absent.)
It seems that keyPathsForValuesAffectingAffectedValue is called in both cases but it is no-op in the latter.
Also, any key paths involving an instance of (subclass of) NSObjectController won't affect other key paths, such as:
#implementation SomeObject
// `someValue` won't be affected by `key.path.(snip).arrangedObjects`
+ (NSSet *)keyPathsForValuesAffectingSomeValue {
return [NSSet setWithObject:#"key.path.involving.anNSArrayController.arrangedObjects"];
}
#end
How do I declare dependency between key paths in such cases? And, why is this whole thing happening?
(Yes, I know about will/didChangeValueForKey: and friends, but wrapping up every affecting key path with a(nother) setter is terrible and I'd like to avoid it.)

NSController and its subclasses are full of KVO "black magic" and unexpected behaviors. (For another example, they don't respect certain KVO options like NSKeyValueObservingOptionPrior) You will be disappointed if you expect them to behave like "normal" objects with respect to KVO. They exist primarily to support Cocoa bindings. Although at first glance bindings may look like mere syntactic sugar on top of KVO, you can see (by overriding the KVO supporting methods of a bound-to object and setting breakpoints in them) that there's actually quite a bit more going on underneath the covers than a simple KVO observation.
Please file bugs with Apple to increase the likelihood that they'll fix (or at least document) these sorts of issues/behavior.

Related

Passing Data from view Controller to NSObject class

I know how to pass the data from one view controller to another view controller ,now i want to know how to pass a textfield value from view controller to NSObject class and how to store the recieved in nstring .Please help me to do this , Can anyone give a example for this ,
I think what you're asking is how to store data in a model object for use by your view controller. If this is not your meaning, then please forgive me.
You are right that a model object should inherit from NSObject. Optionally, you could also extend another model object to add property values. Model objects are a great way to separate the view objects from your data.
Let's say you have a CustomerViewController with some customer fields. You need to populate those customer fields, and potentially perform some processing on that data. The model object supports these relationships, by allowing you to separate your views from any processing logic related to your data and business rules.
Using the relationships below as a guide, you should be on your way to building effective view controllers, that separate your views from your data!
CustomModel Interface
#interface CustomerModel : NSObject
#property (strong, nonatomic) NSString *firstName, *lastName;
#property (strong, nonatomic) NSString *phoneNumber;
- (BOOL) isValidPhoneNumber:(NSString *)phoneNumber;
#end
CustomerModel Implementation
#import "CustomerModel.h"
#implementation CustomerModel
- (BOOL) isValidPhoneNumber:(NSString *)phoneNumber
{
//Check that phone number can be parsed and is valid
}
#end
CustomerViewController Implementation
#import "CustomerViewController.h"
#import "CustomerModel.h"
#interface CustomerViewController () <UITextFieldDelegate>
#property (strong, nonatomic) CustomerModel *customerModel;
...
#property (weak, nonatomic) IBOutlet UITextField *firstNameField
...
#end
#implementation CustomerViewController
- (void) viewDidLoad {
//Optionally instantiate the customer model with stored data,
// to pre-populate the view controller.
self.customerModel = [CustomerModel new];
self.firstNameField.delegate = self;
}
- (void) textFieldDidEndEditing:(UITextField *)textField {
//Validate the phone number
NSString *phoneNumber = textField.text;
if ([self.customerModel isValidPhoneNumber:phoneNumber]) {
self.customerModel.phoneNumber = phoneNumber;
} else {
//Alert the user that the data is invalid
}
}
- (BOOL) textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
#end
Possible improvements
It might be a little annoying to the User, to see alerts while filling out information. So, it might be better to defer the validation to when a save button is pressed.
Model objects can be populated from a data store, to be used to pre-populate a form.

inherit methods declared in .m file

I now know there is no protected method in Objective-C and here is my problem.
I have two viewControllers with many functions and properties that are shared. My vision was to have a BaseViewController holding the shared methods and properties, and from it two classes will inherit and override the needed functionality while using the same variables,
I don't wish to convert the shared functions to public by placing them in the .h file
To help clarify my question I'm adding code :)
#interface BaseViewController ()
#property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *uiButtons;
- (void)setBtns:(NSArray *)p_btns; //tried with & without this line
#end
#implementation BaseViewController
- (void)setBtns:(NSArray *)p_btns {
uiButtons = p_btns;
//do something generic with the buttons (set font, image etc.)
}
#end
#interface DerivedViewController ()
#property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *buttonsConnectedToTheActualView;
#end
#implementation DerivedViewController
- (void) setBtns:(NSArray *)p_btns {
[super setBtns:p_btns];
//do something specific with the buttons (decide if they face up or down according to this class logic)
}
#end
The call to [super setBtns:p_btns]; raises an error:
DerivedGameViewController.m:No visible #interface for 'BaseViewController' declares the selector 'setBtns:'
How can I achieve this? Can someone post a snippet or point to my mistake (in code or concept).
Just create a second header with the protected methods declared in a category. Name and document the header appropriately.
UIGestureRecognizer.h and UIGestureRecognizerSubclass.h may server you as an example.

Objective-C KVO doesn't work with C unions

I need to observe union-typed properties on an Objective-C class using KVO, but it seems I have no luck with this. I did some experiments: everything works fine as long as I am using a C struct. As soon as I replace the struct with a union, automatic KVO doesn't work anymore (observeValueForKeyPath is not being called).
Here's my small test class:
AppDelegate.h:
#import <Cocoa/Cocoa.h>
typedef union {
float data[3];
struct {
float x,y,z;
};
} vec3union;
typedef struct {
float x,y,z;
} vec3struct;
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow *window;
#property (assign) vec3struct vectorStructValue;
#property (assign) vec3union vectorUnionValue;
#end
AppDelegate.m:
#implementation AppDelegate
#synthesize vectorStructValue = _vectorStructValue;
#synthesize vectorUnionValue = _vectorUnionValue;
- (void)dealloc
{
[super dealloc];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self addObserver:self forKeyPath:#"vectorStructValue" options:NSKeyValueObservingOptionNew context:nil];
[self addObserver:self forKeyPath:#"vectorUnionValue" options:NSKeyValueObservingOptionNew context:nil];
self.vectorStructValue = (vec3struct){1,2,3};
self.vectorUnionValue = (vec3union){4,5,6};
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"keyPath %# did change, object: %#", keyPath, [object description]);
}
#end
Output:
2013-01-12 17:38:26.447 KVOTest[57522:303] keyPath vectorStructValue did change, object: <AppDelegate: 0x100614200>
Am I doing something wrong or is this a bug or missing feature in the Objective-C runtime/KVO implementation?
Note: I know I can implement this manually, by overriding the property setter, but this is not the point of this question. The answer should give me an idea of why the automatic KVO doesn't work in this case.
Update: Just to make this clear, this is a simple test case comparing the KVO observer on a struct property to that on a union property. These properties are not interrelated. They have independent ivars with independent memory backing stores. You can remove the struct property and run the test, still the same result – no KVO observer event for the union property.
The properties aren't related in OP's question. I misread that in a fever induced hallucination.
Unions are just flat out busted in KVO/KVC. Leaving the text below because it is still interesting.
KVO doesn't work by watching memory or playing any such tricky shenanigans like that. It works by dynamically creating a subclass on the fly, overriding the setter method, and invoking the willChange.../didChange... methods automatically when the setter is called.
Thus, you effectively have 2 properties with 1 backing store. As far as KVO is concerned, though, they are in total isolation from each other.
What you want is dependent keys. You can use +keyPathsForValuesAffectingValueForKey: to create a dependency between the two keys such that calling either setter will trigger a change for the other property.
I don't know if it supports co-dependnence; if it supports what would effectively be a circular dependency.
Alternatively, you ought to be able to override the setter to call willChange/didChange for the other property (as well as the property being changed).
The related keys would be used if you want willChange/didChange to fire for both keys if either property changes. I.e. if you muck with the struct, the union effectively changes and observers of the union property should see a will/did change in response to setting the struct version.
I just tested it. You're right. Something is odd with unions. It is flat out broken. All of the above still remains true, but it does no good.
Radar filed: rdar://problem/13003794
Oooh... neat. KVO w/unions simply doesn't work. It appears that the runtime simply does not even recognize that the class has a key called vectorUnionValue at all.
I added:
+ (NSSet *)keyPathsForValuesAffectingVectorStructValue
{
return [NSSet setWithObject:#"vectorUnionValue"];
}
+ (NSSet *)keyPathsForValuesAffectingVectorUnionValue
{
return [NSSet setWithObject:#"vectorStructValue"];
}
Which caused a runtime exception:
2013-01-12 12:05:11.877 djkdfjkdfjkdf[51598:303] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<AppDelegate 0x10010a520> valueForUndefinedKey:]: this class is not key value coding-compliant for the key vectorUnionValue.'

If I write a custom property getter method, will KVO still operate if the getter returns a value by accessing a value from another object?

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.

NSManagedObject implementing Protocol - Warnings due to #dynamic

I have the following problem:
As our app is going to be shown to potential customers it has to run offline in first version (due to dependencies to a backend that is not finished right now) and therefore all the data shown in the app will be retrieved by CoreData from a sqlite db.
As we want to avoid later refactoring we decided to put the CoreData entities behind protocols. A service will take care of all the data retrieval and hide the entities with the corresponding protocols.
In a later version of the app the UI developers will just switch the service to the backend one's and do not have to alter the rest of their code due to sticking to the protocols. For UI developer it does not make a difference if the entities will be NSManagedObjects or just NSObjects.
And now comes the problem.
We have declared the protocols (set- and get-Methods) for all the entities in our app and generated CoreData entities that fit to those protocols. CoreData is using #dynamic for all the set/get-Methods (that will we be generated during runtime from the CoreData framework).
All the NSManagedObjects should now implement their corresponding protocols, but the compiler is giving out warnings (because of #dynamic) that the CoreData-Object is not implementing the protocol.
I just want to give you ONE entity and the corresponding protocol to explain my problem in detail:
TaskCD.h
#interface TaskCD : NSManagedObject<Task> {
#private
}
#property (nonatomic, retain) NSString * category;
#property (nonatomic, retain) NSNumber * frequency;
#property (nonatomic, retain) NSDate * validityEnd;
#property (nonatomic, retain) NSDate * validityStart;
#property (nonatomic, retain) NSNumber * periodicity;
#property (nonatomic, retain) NSString * descr;
#property (nonatomic, retain) NSNumber * selected;
#property (nonatomic, retain) NSSet* measurements;
#end
TaskCD.m
#import "TaskCD.h"
#import "MeasurementCD.h"
#implementation TaskCD
#dynamic category;
#dynamic frequency;
#dynamic validityEnd;
#dynamic validityStart;
#dynamic periodicity;
#dynamic descr;
#dynamic selected;
#dynamic measurements;
.... CoreData One-To-Many-stuff ....
#end
Task.h
#protocol Task <NSObject>
- (NSString*) getCategory;
- (void) setCategory:(NSString*) category;
- (NSNumber*) getFrequency;
- (void) setFrequency:(NSNumber*) frequency;
- (void) setValidityEnd:(NSDate *) date;
- (NSDate *) getValidityEnd;
- (void) setValidityStart:(NSDate *) date;
- (NSDate *) getValidityStart;
- (void) setPeriodicity:(NSNumber *) number;
- (NSNumber *) getPeriodicity;
- (void) setDescr:(NSString *) descr;
- (NSString *) getDescr;
- (void) setSelected:(NSNumber *) selected;
- (NSNumber *) getSelected;
- (void) setMeasurements:(NSSet*) measurements;
- (NSSet *) getMeasurements;
#end
I am not that experienced with ObjC and I am coming from Java Development. Maybe it is a design failure and not an ObjC issue. What we want to definitely stick with, is that in the productive version with the real backend the UI developer should NOT work with NSManageObject classes to keep away the CoreData stuff. He just sees one facade giving him an API to interact with the layers behind (First CoreData. Later REST backend). The UI developer should only see plain VOs or stick to protocols (interfaces).
I just want to know how to avoid those warnings. All proposals are welcome. ;)
Thanks in advance guys!!
One thing I can suggest off the bat: the accessors defined in Task.h should match the names of the #property declarations in the TaskCD interface. Instead of:
- (NSString*) getCategory;
you should declare it as:
// this is the more common Obj-C naming convention
- (NSString*) category;
Alternately, specify the name of the "get" accessor in the #property declaration (in TaskCD.h):
//meh... not so nice
#property (nonatomic, retain, getter=getCategory) NSString * category;
Also IMHO, with Core Data you're much better off using the dynamically generated accessors, rather than implementing your own.
First, I want to say that I agree with octy in that the best solution would probably be just to change your protocol to use more standard Objective-C accessor names.
However, I also wanted to throw out the option that you could create a category for each of your generated CoreData objects that essentially maps their accessor methods to the accessor names you want:
#interface TaskCD (Accessors) <Task>
...
#implementation TaskCD (Accessors)
- (NSString *) getCategory {
return [self category];
}
...
This way, you can keep the generated files (and regenerate them however often you like) and still use your own accessor method names.
That being said, I still think it would be better to just stick with the Objective-C default accessor names.
there is another approach you could follow: keep core data even in your later REST version and simply update your local core data storage with the live data respectively use the NSManagedObjects. using core data in UI stuff is actually a good idea, because the you get a lot of benefits (e.g. feeding tableviews which successively fetch rows from core data when scrolling)