I was writing an MVC app today. I noticed that to expose a binding, you have to write a lot of code for each one, all which are much alike. Like you have to observe value changes, store the observable and the key path, etc.
Am I overlooking something here or is exposing bindings a pain? Is there a reason why there's no standard API to keep two properties (or KVC/KVO compliant attributes) in sync?
To clarify, just in case, here's one of my classes to see what I'm talking about.
Am I overlooking something here or is exposing bindings a pain? Is there a reason why there's no standard API to keep two properties (or KVC/KVO compliant attributes) in sync?
To clarify, just in case, here's one of my classes to see what I'm talking about.
Code:
//
// DimpBaseView.h
// Dimp
//
// Created by Sijmen Mulder on 20-03-08.
// Copyright 2008 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class Bone;
@interface DimpBaseView : NSView
{
id rootBoneBindingObject;
id currentTimeBindingObject;
NSString *rootBoneBindingPath;
NSString *currentTimeBindingPath;
Bone *rootBone;
float currentTime;
}
@property (readwrite, retain) Bone *rootBone;
@property (readwrite) float currentTime;
@end
//
// DimpBaseView.m
// Dimp
//
// Created by Sijmen Mulder on 20-03-08.
// Copyright 2008 __MyCompanyName__. All rights reserved.
//
#import "DimpBaseView.h"
static void *RootBoneBindingContext = (void *)@"RootBoneBindingContext";
static void *CurrentTimeBindingContext = (void *)@"CurrentTimeBindingContext";
@implementation DimpBaseView
- (void)bind:(NSString *)binding toObject:(id)observable withKeyPath:(NSString *)keyPath options:(NSDictionary *)options
{
if ([binding isEqualToString:@"rootBone"])
{
if (rootBoneBindingObject)
[self unbind:binding];
rootBoneBindingObject = observable;
rootBoneBindingPath = keyPath;
[rootBoneBindingObject addObserver:self forKeyPath:keyPath options:0 context:RootBoneBindingContext];
}
else if ([binding isEqualToString:@"currentTime"])
{
if (currentTimeBindingObject)
[self unbind:binding];
currentTimeBindingObject = observable;
currentTimeBindingPath = keyPath;
[currentTimeBindingObject addObserver:self forKeyPath:keyPath options:0 context:CurrentTimeBindingContext];
}
else
{
[super bind:binding toObject:observable withKeyPath:keyPath options:options];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == RootBoneBindingContext)
{
[self setValue:[rootBoneBindingObject valueForKey:rootBoneBindingPath] forKey:@"rootBone"];
}
else if (context == CurrentTimeBindingContext)
{
[self setValue:[currentTimeBindingObject valueForKey:currentTimeBindingPath] forKey:@"currentTime"];
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)unbind:(NSString *)binding
{
if ([binding isEqualToString:@"rootBone"])
{
[rootBoneBindingObject removeObserver:self forKeyPath:rootBoneBindingPath];
rootBoneBindingObject = nil;
rootBoneBindingPath = nil;
}
else if ([binding isEqualToString:@"currentTime"])
{
[currentTimeBindingObject removeObserver:self forKeyPath:currentTimeBindingPath];
currentTimeBindingObject = nil;
currentTimeBindingPath = nil;
}
else
{
[super unbind:binding];
}
}
- (Bone *)rootBone
{
return rootBone;
}
- (void)setRootBone:(Bone *)value
{
if (rootBone == value)
return;
rootBone = value;
if (rootBoneBindingObject)
[rootBoneBindingObject setValue:value forKeyPath:rootBoneBindingPath];
[self didChangeValueForKey:@"rootBone"];
[self setNeedsDisplay:YES];
}
- (float)currentTime
{
return currentTime;
}
- (void)setCurrentTime:(float)value
{
if (currentTime == value)
return;
currentTime = value;
if (currentTimeBindingObject)
[currentTimeBindingObject setValue:[NSNumber numberWithFloat:value] forKeyPath:currentTimeBindingPath];
[self didChangeValueForKey:@"rootBone"];
[self setNeedsDisplay:YES];
}
@end