This is a very wide-ranging/vague question, but here goes. Apologies in advance.
The app (desktop app) I'm building takes different kinds of input to generate a QR code (I'm just building it to learn some Obj-C/Cocoa). The user can switch between different views that allow input of plain text (single text field), VCard/MeCard data (multiple text fields), and other stuff. No matter the input, the result is a QR code.
To keep things contained, I'd like to use the views as view-controllers, so they handle they're own inputs, and can simply "send" a generic "data to encode" object containing all the data to a central encoder. I.e. the plain text view would make a data object with its textfield's text, while the VCard/MeCard view would use all of its fields to make structured VCard/MeCard data.
I can bind all of this stuff together manually in code, but I'd really like to learn how bindings/KVO could help me out. Alas, after reading Apple's developer docs, and the simpler tutorials/examples I could find, I'm still not sure how to apply it to my app.
For instance: The user edits the textfields in the VCard-view. The VCard view-controller is notified of each update and "recalculates" the data object. The central encoder controller is then notified of the updated data object, and encodes the data.
The point of all this, is that the input views can be created completely independently, and can contain all kinds of input fields. They then handle their own inputs, and "return" a generic data object, which the encoder can use. Internally, the views observe their inputs to update the data object, and externally the encoder needs only observe the data object.
Trouble is I have no idea how to make this all happen and keep it decoupled. Should there be an object controller between the input-view and its fields? Should there be another one between the view and the encoder? What do I need where? If anyone has a link to a good tutorial, please share.
Again, I can roll my own system of notifications and glue code, but I think the point is to avoid that.
Definitely a vague question, but one beginner to another, I feel your pain :)
I downloaded and unpacked every single example and grep through them frequently. I've found that to be the most valuable thing to get me over the hump. I definitely recommend not giving up on the examples. I hacked up this script to download and unpack them all.
In terms of good KVO patterns, I found the technique described here to be very useful. It doesn't work as-is in Objective-C 2.0 however. Also he doesn't give much detail on how it's actually used. Here's what I've got working:
The KVODispatcher.h
like this:
#import <Foundation/Foundation.h>
@interface KVODispatcher : NSObject {
id owner;
}
@property (nonatomic, retain) id owner;
- (id) initWithOwner:(id)owner;
- (void)startObserving:(id)object keyPath:(NSString*)keyPath
options:(NSKeyValueObservingOptions)options
selector:(SEL)sel;
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context;
@end
And the KVODispatcher.m
is as so:
#import "KVODispatcher.h"
#import <objc/runtime.h>
@implementation KVODispatcher
@synthesize owner;
- (id)initWithOwner:(id)theOwner
{
self = [super init];
if (self != nil) {
self.owner = theOwner;
}
return self;
}
- (void)startObserving:(id)object
keyPath:(NSString*)keyPath
options:(NSKeyValueObservingOptions)options
selector:(SEL)sel
{
// here is the actual KVO registration
[object addObserver:self forKeyPath:keyPath options:options context:sel];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// The event is delegated back to the owner
// It is assumed the method identified by the selector takes
// three parameters 'keyPath:object:change:'
objc_msgSend(owner, (SEL)context, keyPath, object, change);
// As noted, a variation of this technique could be
// to expand the data passed in to 'initWithOwner' and
// have that data passed to the selected method here.
}
@end
Then you can register to observe events like so:
KVODispatcher* dispatcher = [[KVODispatcher alloc] initWithOwner:self];
[dispatcher startObserving:theObject
keyPath:@"thePath"
options:NSKeyValueChangeNewKey
selector:@selector(doSomething:object:change:)];
And in the same object that executed the above, you can have a method like so:
- (void) doSomething:(NSString *)keyPath
object:(id)object
change:(NSDictionary *)change {
// do your thing
}
You can have as many of these "doSomething" type methods as you like. Just as long as they use the same parameters (keyPath:object:change:) it will work out. With one dispatcher per object that wishes to receive any number of notifications about changes in any number of objects.
What I like about it:
observeValueForKeyPath
per class, but you may want to observe several things. Natural next thought is "hey maybe I can pass a selector"performSelector
unless wrapper objects like NSNotification
are used. Who wants to clean up wrapper objects.observeValueForKeyPath
when a superclass also uses KVO makes any generic approaches hard -- you have to know which notifications to pass to the super class and which to keep.observeValueForKeyPath
in every object anyway? Better to just do it once and reuse it.A nice variation might be to add another field like id additionalContext
to KVODispatcher and have that additionalContext object passed in the objc_msgSend
call. Could be useful to use it to stash a UI object that needs to get updated when the observed data changes. Even perhaps an NSArray.