/* * CPKeyValueCoding.j * Foundation * * Created by Francisco Tolmasky. * Copyright 2008, 280 North, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ import "CPArray.j" import "CPDictionary.j" import "CPException.j" import "CPObject.j" @implementation CPObject (KeyValueObserving) - (void)willChangeValueForKey:(CPString)aKey { } - (void)didChangeValueForKey:(CPString)aKey { } - (void)addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(unsigned)options context:(id)aContext { if (!anObserver || !aPath) return; [[_CPKVOProxy proxyForObject:self] _addObserver:anObserver forKeyPath:aPath options:options context:aContext]; } - (void)removeObserver:(id)anObserver forKeyPath:(CPString)aPath { if (!anObserver || !aPath) return; [[KVOProxyMap objectForKey:[self hash]] _removeObserver:anObserver forKeyPath:aPath]; } - (BOOL)automaticallyNotifiesObserversForKey:(CPString)aKey { return YES; } @end // KVO Options CPKeyValueObservingOptionNew = 1 << 0; CPKeyValueObservingOptionOld = 1 << 1; CPKeyValueObservingOptionInitial = 1 << 2; CPKeyValueObservingOptionPrior = 1 << 3; //convenience var kvoNewAndOld = CPKeyValueObservingOptionNew|CPKeyValueObservingOptionOld; // KVO Change Dictionary Keys CPKeyValueChangeKindKey = @"CPKeyValueChangeKindKey"; CPKeyValueChangeNewKey = @"CPKeyValueChangeNewKey"; CPKeyValueChangeOldKey = @"CPKeyValueChangeOldKey"; CPKeyValueChangeIndexesKey = @"CPKeyValueChangeIndexesKey"; CPKeyValueChangeNotificationIsPriorKey = @"CPKeyValueChangeNotificationIsPriorKey"; // Map of real objects to their KVO proxy var KVOProxyMap = [CPDictionary dictionary]; //rule of thumb: _ methods are called on the real proxy object, others are called on the "fake" proxy object (aka the real object) /* @ignore */ @implementation _CPKVOProxy : CPObject { id _targetObject; Class _nativeClass; CPDictionary _changesForKey; CPDictionary _observersForKey; CPDictionary _replacementMethods; } + (id)proxyForObject:(CPObject)anObject { var proxy = [KVOProxyMap objectForKey:[anObject hash]]; if (proxy) return proxy; proxy = [[self alloc] initWithTarget:anObject]; //[proxy _replaceSetters]; //anObject.isa = proxy.isa; [proxy _replaceClass]; [KVOProxyMap setObject:proxy forKey:[anObject hash]]; return proxy; } - (id)initWithTarget:(id)aTarget { self = [super init]; _targetObject = aTarget; _nativeClass = [aTarget class]; _replacementMethods = [CPDictionary dictionary]; _observersForKey = [CPDictionary dictionary]; _changesForKey = [CPDictionary dictionary]; return self; } - (void)_replaceSetters { var currentClass = [_targetObject class]; while (currentClass && currentClass != currentClass.super_class) { var methodList = currentClass.method_list, count = methodList.length; for (var i=0; i 2 || !([selector hasPrefix:@"set"] || [selector hasPrefix:@"_set"])) return nil; var keyIndex = selector.indexOf("set") + "set".length, colonIndex = selector.indexOf(":"); return selector.charAt(keyIndex).toLowerCase() + selector.substring(keyIndex+1, colonIndex); } @implementation CPArray (KeyValueObserving) - (void)addObserver:(id)anObserver toObjectsAtIndexes:(CPIndexSet)indexes forKeyPath:(CPString)aKeyPath options:(unsigned)options context:(id)context { var index = [indexes firstIndex]; while (index >= 0) { [self[index] addObserver:anObserver forKeyPath:aKeyPath options:options context:context]; index = [indexes indexGreaterThanIndex:index]; } } - (void)removeObserver:(id)anObserver fromObjectsAtIndexes:(CPIndexSet)indexes forKeyPath:(CPString)aKeyPath { var index = [indexes firstIndex]; while (index >= 0) { [self[index] removeObserver:anObserver forKeyPath:aKeyPath]; index = [indexes indexGreaterThanIndex:index]; } } -(void)addObserver:(id)observer forKeyPath:(CPString)aKeyPath options:(unsigned)options context:(id)context { [CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPArray"]; } -(void)removeObserver:(id)observer forKeyPath:(CPString)aKeyPath { [CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPArray"]; } @end