KVO源码阅读。
尝试去读一遍KVO的源码,发现它比KVC复杂的多,只能明白个大概。 文章最后按照自己的理解,再结合源码的实现流程自己写了一个KVO,锻炼锻炼。
一、常用接口 1 2 3 4 5 6 7 8 9 10 11 @interface NSObject (NSKeyValueObserving )- (void )observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id )object change:(nullable NSDictionary <NSKeyValueChangeKey , id > *)change context:(nullable void *)context; @end @interface NSObject (NSKeyValueObserverRegistration )- (void )addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions )options context:(nullable void *)context; - (void )removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7 ), ios(5.0 ), watchos(2.0 ), tvos(9.0 )); - (void )removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; @end
二、技巧回顾 1.如何使得属性在未改变的情况下不发送通知(手动控制)? 示例如下,主要有:
重写automaticallyNotifiesObserversForKey:
方法
重写setter
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @interface YAObject : NSObject @property (nonatomic , copy ) NSString *name;@end @implementation YAObject + (BOOL )automaticallyNotifiesObserversForKey:(NSString *)key { if ([key isEqualToString:@"name" ]) return NO ; return [super automaticallyNotifiesObserversForKey:key]; } - (void )setName:(NSString *)name { if (name != _name) { [self willChangeValueForKey:@"name" ]; _name = name; [self didChangeValueForKey:@"name" ]; } } @end
2.如何注册依赖键(多个属性影响到某一个属性)?
重写getter
方法,定义依赖关系
重写keyPathsForValuesAffectingValueForKey:
方法,添加依赖的key集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @interface YAObject : NSObject @property (nonatomic , copy ) NSString *name;@property (nonatomic , copy ) NSString *firstName;@property (nonatomic , copy ) NSString *lastName;@end @implementation YAObject - (NSString *)name { return [NSString stringWithFormat:@"firstName is %@ and lastName is %@" , self .firstName, self .lastName]; } + (NSSet <NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key { NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; if ([key isEqualToString:@"name" ]) { keyPaths = [keyPaths setByAddingObjectsFromArray:@[@"firstName" , @"lastName" ]]; } return keyPaths; } @end
3.当NSMutableArray中元素增加和减少时如何监听到? 示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @interface YAObject : NSObject@property (nonatomic, strong) NSMutableArray *nameList;@end @implementation YAObject- (NSUInteger)countOfNameList { return [_nameList count] ; } - (id)objectInNameListAtIndex :(NSUInteger)index { return [_nameList objectAtIndex:index] ; } - (void)insertObject :(id)object inNameListAtIndex :(NSUInteger)index { [_nameList insertObject:object atIndex:index] ; } - (void)removeObjectFromNameListAtIndex :(NSUInteger)index { [_nameList removeObjectAtIndex:index] ; } @end
使用:
务必使用mutableArrayValueForKey
获取集合
使用insertObject
方法添加元素
比如:
1 2 NSMutableArray *nameList = [self.obj mutableArrayValueForKey: @"nameList" ]; [nameList insertObject: obj atIndex: 0 ];
三、几个问题 总结的几个问题。
1.KVO的实现原理是什么?
Objective-C依托于强大的runtime机制来实现KVO。当我们第一次观察某个对象的属性时,runtime会创建一个新的继承自这个对象的class的subclass(前缀是NSKVONotifying_
)。在这个新的subclass中,它会重写所有被观察的key的setter,然后将object的isa指针指向新创建的class(这个指针告诉Objective-C运行时某个object到底是什么类型的)。所以object神奇地变成了新的子类的实例。
——摘自南峰子的博客。
新类重写了setter
方法、class
方法、dealloc
方法和_isKVOA
方法。如果自己生成重名的NSKVONotifying_XX类,会造成KVO失效。以监听YAPerson对象的age属性为例,主要逻辑的伪代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @interface YAPerson : NSObject@property (nonatomic, assign) NSUInteger age;@end @implementation YAPerson@end @interface NSKVONotifying_YAPerson : YAPerson@end @implementation NSKVONotifying_YAPerson- (void)setAge :(NSUInteger)age { _NSSetUnsignedLongLongValueAndNotify (); } void _NSSetUnsignedLongLongValueAndNotify () { [self willChangeValueForKey:@"age"] ; [super setAge:age] ; [self didChangeValueForKey:@"age"] ; } - (void)didChangeValueForKey :(NSString *)key { [observer observeValueForKeyPath:@"age" ofObject:self change:change context:context] ; } @end
2.KVO源码中添加观察者时整体的大致流程是什么?
将keyPath
、class
等信息封装成NSKeyValueProperty
,分别解析一般属性(@"aa"
)、可计算属性(@"@aa"
)、属性链(@"aa.bb.@cc.dd"
),进行子类化,缓存在CFMutableSet
中方便下次快速取出。
将NSKeyValueProperty
、context
、options
、observer
等信息封装成NSKeyValueObservance
,缓存在NSHashTable
中。
倘若设置了NSKeyValueObservingOptionInitial
选项,会在注册观察服务时调用一次触发方法。
动态创建名为NSKVONotifying_+原来类名
的新类,重写其dealloc
、_isKVOA
方法,再重写class
方法,利用object_setClass()
函数将其isa指针指向原先的类。
重写willChangeValueForKey:
和didChangeValueForKey:
方法,重写被观察属性的setter
方法,在setter
中先调用willChangeValueForKey:
方法,然后调用父类的 setter
方法对成员变量赋值,之后再调用 didChangeValueForKey:
方法。
didChangeValueForKey:
方法中会调用observeValueForKeyPath:ofObject:change:context:
方法。
3.KVO中所封装组件的关系是怎样的?
将keyPath、class
等信息封装成NSKeyValueProperty
,使用CFMutableSet
缓存NSKeyValueProperty
。
将observer、property、options、context 、originalObservable
等信息封装成NSKeyValueObservance
,使用NSHashTable(NSKeyValueShareableObservationInfos)
缓存。
NSKeyValueObservationInfo
与NSKeyValueObservance
的关系是: NSKeyValueObservationInfo
中有一个observances
数组,数组里面是NSKeyValueObservance
对象。
每一个object
都有一个observationInfo
属性(void *
类型),它与NSKeyValueObservationInfo
会相互转化。
class
和keyPath
决定了是否是同一个NSKeyValueProperty
。NSKeyValueProperty
、Observer
、options
、context
决定了是否是同一个NSKeyValueObservance
。
4.KVO多次使用完全相同的参数进行addObserver操作,也会得到相应次数的回调,如何做到? 1 2 3 4 [self.obj addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: NULL]; [self.obj addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: NULL]; [self.obj addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: NULL]; [self.obj addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: NULL];
如上面代码,会有四次回调。
在每次添加观察者时,都会获取NSKeyValueObservance
对象(可能从缓存中获取也可能新建),并把它追加到object的observances
数组中(即使该数组中已经存在完全相同(指针一致)的NSKeyValueObservance
对象),由此保证了多次addObserver
操作会有多次回调。
通过打印可以证明:
5.如何手动触发KVO? 手动调用willChangeValueForKey:
和didChangeValueForKey:
。也即:1 2 3 4 - (void )invokeKVOForAge { [self.person willChangeValueForKey:@"age" ]; [self.person didChangeValueForKey:@"age" ]; }
只手动调用一个还不行,为什么?由原理可知,didChangeValueForKey:
中才真正调用了observeValueForKeyPath:ofObject:change:context
方法,所以didChangeValueForKey:
毋庸置疑不可或缺。那么为什么还需要willChangeValueForKey:
呢? 查看文档,苹果一如既往地冷酷:
Calls to this method are always paired with a matching call to willChangeValueForKey:.
意思是,这俩是成对存在的,你只管调就是了。好吧,这块源码确实没看明白,先挖个坑吧。
6.通过KVC修改属性会触发KVO吗? 当KVC是通过setter方法设值时,无需多言,会触发KVO。当KVC是通过成员变量设值时,也会触发KVO,为什么? 阅读源码可知,直接通过成员变量设值,会创建NSKeyValueIvarSetter对象,在该对象的构造方法中会调用_NSSetValueAndNotifyForKeyInIvar()
函数,其实现如下:1 2 3 4 5 6 7 void _NSSetValueAndNotifyForKeyInIvar(id object, SEL selector, id value, NSString *key, Ivar ivar, IMP imp) { [object willChangeValueForKey:key]; ((void (*)(id ,SEL,id ,NSString *, Ivar))imp)(object,NULL ,value,key,ivar); [object didChangeValueForKey:key]; }
真相大白了,它调用了willChangeValueForKey:
和didChangeValueForKey:
方法,正是这个触发了KVO。需要注意的是,直接修改成员变量是不会触发KVO的 。
四、添加观察者 这一步骤中封装出了许多的类,同时也把产生的许多对象做了进一步的缓存处理。
1.封装 1.1接口方法 加锁,接口方法中使用pthread
中的pthread_mutex_lock()
和pthread_mutex_unlock
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 - (void )d_addObserver: (NSObject *)observer forKeyPath: (NSString *)keyPath options: (NSKeyValueObservingOptions)options context: (void *)context { pthread_mutex_lock(&_NSKeyValueObserverRegistrationLock); _NSKeyValueObserverRegistrationLockOwner = pthread_self(); NSKeyValueProperty *property = NSKeyValuePropertyForIsaAndKeyPath(object_getClass(self),keyPath); [self _d_addObserver: observer forProperty: property options: options context: context]; pthread_mutex_unlock(&_NSKeyValueObserverRegistrationLock); }
1.2.添加对NSKeyValueProperty的观察的具体实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 - (void )_addObserver:(id )observer forProperty:(NSKeyValueProperty *)property options:(int )options context:(void *)context { if (options & NSKeyValueObservingOptionInitial ) { NSString *keyPath = [property keyPath]; _NSKeyValueObserverRegistrationLockOwner = NULL ; pthread_mutex_unlock(&_NSKeyValueObserverRegistrationLock); id newValue = nil ; if (options & NSKeyValueObservingOptionNew ) { newValue = [self valueForKeyPath:keyPath]; if (!newValue) { newValue = [NSNull null]; } } NSKeyValueChangeDictionary *changeDictionary = nil ; NSKeyValueChangeDetails changeDetails = {0 }; changeDetails.kind = NSKeyValueChangeSetting ; changeDetails.oldValue = nil ; changeDetails.newValue = newValue; changeDetails.indexes = nil ; changeDetails.extraData = nil ; NSKeyValueNotifyObserver (observer,keyPath, self , context, nil , NO ,changeDetails, &changeDictionary); [changeDictionary release]; pthread_mutex_lock(&_NSKeyValueObserverRegistrationLock); _NSKeyValueObserverRegistrationLockOwner = pthread_self(); } NSKeyValueObservationInfo *oldObservationInfo = _NSKeyValueRetainedObservationInfoForObject(self ,property.containerClass); BOOL cacheHit = NO ; NSKeyValueObservance *addedObservance = nil ; id originalObservable = nil ; if ((options >> 8 ) & 0x01 ) { NSKeyValueObservingTSD *TSD = _CFGetTSD(NSKeyValueObservingTSDKey ); if (TSD) { originalObservable = TSD->implicitObservanceAdditionInfo.originalObservable; } } NSKeyValueObservationInfo *newObservationInfo = _NSKeyValueObservationInfoCreateByAdding(oldObservationInfo, observer, property, options, context, originalObservable,&cacheHit,&addedObservance); _NSKeyValueReplaceObservationInfoForObject(self ,property.containerClass,oldObservationInfo,newObservationInfo); [property object:self didAddObservance:addedObservance recurse:YES ]; Class isaForAutonotifying = [property isaForAutonotifying]; if (isaForAutonotifying) { Class cls = object_getClass(self ); if (cls != isaForAutonotifying) { object_setClass(self ,isaForAutonotifying); } } [newObservationInfo release]; [oldObservationInfo release]; }
函数1: 设置changeDictionary并调用NSKVONotify()函数 1 2 3 4 5 6 7 8 9 10 11 12 void NSKeyValueNotifyObserver (id observer,NSString * keyPath, id object, void *context, id originalObservable, BOOL isPriorNotification, NSKeyValueChangeDetails changeDetails, NSKeyValueChangeDictionary **changeDictionary) { if (*changeDictionary) { [*changeDictionary setDetailsNoCopy:changeDetails originalObservable:originalObservable]; } else { *changeDictionary = [[NSKeyValueChangeDictionary alloc] initWithDetailsNoCopy:changeDetails originalObservable:originalObservable isPriorNotification:isPriorNotification]; } NSUInteger retainCountBefore = [*changeDictionary retainCount]; NSKVONotify (observer, keyPath, object, *changeDictionary, context); if (retainCountBefore != (NSUInteger )INTMAX_MAX && retainCountBefore != [*changeDictionary retainCount]) { [*changeDictionary retainObjects]; } }
1 2 3 4 5 void NSKVONotify (id observer, NSString *keyPath, id object, NSDictionary *changeDictionary, void *context) { NSKeyValueObservingAssertRegistrationLockNotHeld (); [observer observeValueForKeyPath:keyPath ofObject:object change:changeDictionary context:context]; }
函数2: 获取object的observationInfo对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 NSKeyValueObservationInfo *_NSKeyValueRetainedObservationInfoForObject(id object, NSKeyValueContainerClass *containerClass) { NSKeyValueObservationInfo *observationInfo = nil ; os_lock_lock(&NSKeyValueObservationInfoSpinLock ); if (containerClass) { observationInfo = ((NSKeyValueObservationInfo * (*)(id ,SEL))containerClass.cachedObservationInfoImplementation)(object, @selector (observationInfo)); } else { observationInfo = (NSKeyValueObservationInfo *)[object d_observationInfo]; } [observationInfo retain ]; os_lock_unlock(&NSKeyValueObservationInfoSpinLock ); return observationInfo; }
函数3: 获取’添加观察者’时所需要的NSKeyValueObservationInfo 如果baseObservationInfo
存在,则一顿封装操作后,会把封装完毕的NSKeyValueObservance
“追加”到baseObservationInfo
的observances
数组中。如果baseObservationInfo
不存在,则一顿封装操作后,会把封装完毕的NSKeyValueObservance
放到新创建的NSKeyValueObservationInfo
对象的observances
数组中。最后,cacheHit
告诉调用者是否有命中缓存,*addedObservance
指向了observance
对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 NSKeyValueObservationInfo *_NSKeyValueObservationInfoCreateByAdding(NSKeyValueObservationInfo *baseObservationInfo, id observer, NSKeyValueProperty *property, int options, void *context, id originalObservable, BOOL *cacheHit, NSKeyValueObservance **addedObservance) { NSKeyValueObservationInfo *createdObservationInfo = nil ; os_lock_lock(&NSKeyValueObservationInfoCreationSpinLock ); if (!NSKeyValueShareableObservationInfos ) { NSPointerFunctions *pointerFunctions = [[NSPointerFunctions alloc] initWithOptions:NSPointerFunctionsWeakMemory ]; [pointerFunctions setHashFunction:NSKeyValueShareableObservationInfoNSHTHash ]; [pointerFunctions setIsEqualFunction:NSKeyValueShareableObservationInfoNSHTIsEqual ]; NSKeyValueShareableObservationInfos = [[NSHashTable alloc] initWithPointerFunctions:pointerFunctions capacity:0 ]; } if (!NSKeyValueShareableObservationInfoKeyIsa ) { NSKeyValueShareableObservationInfoKeyIsa = [NSKeyValueShareableObservationInfoKey class ]; } static NSKeyValueShareableObservationInfoKey * shareableObservationInfoKey; if (!shareableObservationInfoKey) { shareableObservationInfoKey = [[NSKeyValueShareableObservationInfoKey alloc] init]; } shareableObservationInfoKey.addingNotRemoving = YES ; shareableObservationInfoKey.baseObservationInfo = baseObservationInfo; shareableObservationInfoKey.additionObserver = observer; shareableObservationInfoKey.additionProperty = property; shareableObservationInfoKey.additionOptions = options; shareableObservationInfoKey.additionContext = context; shareableObservationInfoKey.additionOriginalObservable = originalObservable; NSKeyValueObservationInfo * existsObservationInfo = [NSKeyValueShareableObservationInfos member:shareableObservationInfoKey]; shareableObservationInfoKey.additionOriginalObservable = nil ; shareableObservationInfoKey.additionObserver = nil ; shareableObservationInfoKey.baseObservationInfo = nil ; if (!existsObservationInfo) { if (!NSKeyValueShareableObservances ) { NSKeyValueShareableObservances = [NSHashTable weakObjectsHashTable]; } static NSKeyValueShareableObservanceKey *shareableObservanceKey; if (!shareableObservanceKey) { shareableObservanceKey = [[NSKeyValueShareableObservanceKey alloc] init]; } shareableObservanceKey.observer = observer; shareableObservanceKey.property = property; shareableObservanceKey.options = options; shareableObservanceKey.context = context; shareableObservanceKey.originalObservable = originalObservable; NSKeyValueObservance *existsObservance = [NSKeyValueShareableObservances member:shareableObservanceKey]; shareableObservanceKey.originalObservable = nil ; shareableObservanceKey.observer = nil ; NSKeyValueObservance *observance = nil ; if (!existsObservance) { observance = [[NSKeyValueObservance alloc] _initWithObserver:observer property:property options:options context:context originalObservable:originalObservable]; if (observance.cachedIsShareable) { [NSKeyValueShareableObservances addObject:observance]; } } else { observance = existsObservance; } if (baseObservationInfo) { createdObservationInfo = [baseObservationInfo _copyByAddingObservance:observance]; } else { createdObservationInfo = [[NSKeyValueObservationInfo alloc] _initWithObservances:&observance count:1 hashValue:0 ]; } if (createdObservationInfo.cachedIsShareable){ [NSKeyValueShareableObservationInfos addObject:createdObservationInfo]; } *cacheHit = NO ; *addedObservance = observance; } else { *cacheHit = YES ; *addedObservance = existsObservationInfo.observances.lastObject; createdObservationInfo = existsObservationInfo; } os_lock_unlock(&NSKeyValueObservationInfoCreationSpinLock ); return createdObservationInfo; }
函数4:将object的observationInfo设置为newObservationInfo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 void _NSKeyValueReplaceObservationInfoForObject(id object, NSKeyValueContainerClass * containerClass, NSKeyValueObservationInfo *oldObservationInfo, NSKeyValueObservationInfo *newObservationInfo) { os_lock_lock(&NSKeyValueObservationInfoSpinLock ); if (newObservationInfo) [newObservationInfo retain ]; NSKeyValueObservingTSD *TSD = _CFGetTSD(NSKeyValueObservingTSDKey ); if (TSD) { ObservationInfoWatcher *next = TSD->firstWatcher; while (next) { if (next->object == object) { [next->observationInfo release]; next->observationInfo = [newObservationInfo retain ]; break ; } next = next->next; } } if (containerClass) { containerClass.cacheNSetObservationInfoImplementation(object, @selector (d_setObservationInfo:), newObservationInfo); } else { [object d_setObservationInfo: newObservationInfo]; } os_lock_unlock(&NSKeyValueObservationInfoSpinLock ); }
2.缓存 缓存查找逻辑是一致的: 确定这些对象的hash
和isEqual:
方法,通过创建与目标对象判等属性一致的key去查找。
2.1.NSKeyValueProperty的缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if (!NSKeyValueProperties ) { CFSetCallBacks callbacks = {0 }; callbacks.version = kCFTypeSetCallBacks.version; callbacks.retain = kCFTypeSetCallBacks.retain; callbacks.release = kCFTypeSetCallBacks.release; callbacks.copyDescription = kCFTypeSetCallBacks.copyDescription; callbacks.equal = (CFSetEqualCallBack )NSKeyValuePropertyIsEqual ; callbacks.hash = (CFSetHashCallBack )NSKeyValuePropertyHash ; NSKeyValueProperties = CFSetCreateMutable (NULL , 0 , &callbacks); } CFSetAddValue (NSKeyValueProperties , property);
CFSet集合中元素判等的依据
1 2 3 4 BOOL NSKeyValuePropertyIsEqual (NSKeyValueProperty *property1, NSKeyValueProperty *property2) { return (property1.containerClass == property2.containerClass) && (property1.keyPath == property2.keyPath || [property1.keyPath isEqual: property2.keyPath]); }
返回NSKeyValueProperty的hash值
1 2 3 NSUInteger NSKeyValuePropertyHash(NSKeyValueProperty *property ) { return property . keyPath.hash ^ (NSUInteger)(void *)property . containerClass; }
再次证明了 class
和keyPath
决定了是否是同一个NSKeyValueProperty
。
2.2.NSKeyValueObservance的缓存 缓存查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 NSHashTable *NSKeyValueShareableObservances ;if (!NSKeyValueShareableObservances ) { NSKeyValueShareableObservances = [NSHashTable weakObjectsHashTable]; } static DNSKeyValueShareableObservanceKey *shareableObservanceKey;if (!shareableObservanceKey) { shareableObservanceKey = [[DNSKeyValueShareableObservanceKey alloc] init]; } shareableObservanceKey.observer = observer; shareableObservanceKey.property = property; shareableObservanceKey.options = options; shareableObservanceKey.context = context; shareableObservanceKey.originalObservable = originalObservable; NSKeyValueObservance *existsObservance = [NSKeyValueShareableObservances member:shareableObservanceKey];shareableObservanceKey.originalObservable = nil ; shareableObservanceKey.observer = nil ;
重写NSKeyValueObservance
的hash
和isEqual:
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 - (NSUInteger )hash { return _NSKVOPointersHash(5 , _observer, _property, (void *)(NSUInteger )(_options), _context, _originalObservable); } - (BOOL )isEqual:(id )object { if (object == self ) return YES ; if (![object isKindOfClass: self .class]) return NO ; NSKeyValueObservance *other = (NSKeyValueObservance *)object; return other.observer == self .observer && other.options == self .options && other.context == self .context && other.originalObservable == self .originalObservable; }
2.3.NSKeyValueObservationInfo的缓存 缓存查找
1 2 3 4 5 6 7 8 9 NSHashTable *NSKeyValueShareableObservationInfos ;if (!NSKeyValueShareableObservationInfos ) { NSPointerFunctions *pointerFunctions = [[NSPointerFunctions alloc] initWithOptions:NSPointerFunctionsWeakMemory ]; [pointerFunctions setHashFunction:NSKeyValueShareableObservationInfoNSHTHash ]; [pointerFunctions setIsEqualFunction:NSKeyValueShareableObservationInfoNSHTIsEqual ]; NSKeyValueShareableObservationInfos = [[NSHashTable alloc] initWithPointerFunctions:pointerFunctions capacity:0 ]; }
2.4.NSKeyValueObservationInfo的存储(observationInfo) 当封装成NSKeyValueObservationInfo
时,weak
的NSHashTable
并不负责存储,那么,谁负责真正的存储呢?
Take or return a pointer that identifies information about all of the observers that are registered with the receiver, the options that were used at registration-time, etc. The default implementation of these methoNS store observation info in a global dictionary keyed by the receivers’ pointers. For improved performance, you can override these methoNS to store the opaque data pointer in an instance variable. Overrides of these methoNS must not attempt to send Objective-C messages to the passed-in observation info, including -retain and -release.
这个方法的默认实现是以对象的指针作为key,从一个全局的字典中获取信息。
如何获取对象的指针?这里有个定义:OBSERVATION_INFO_KEY
的定义是: #define OBSERVATION_INFO_KEY(object) ((void *)(~(NSUInteger)(object)))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 CFMutableDictionaryRef NSKeyValueObservationInfoPerObject = NULL ;- (void *)observationInfo { return NSKeyValueObservationInfoPerObject ? (void *)CFDictionaryGetValue (NSKeyValueObservationInfoPerObject , OBSERVATION_INFO_KEY(self )) : NULL ; } - (void )setObservationInfo:(void *)info { if (!NSKeyValueObservationInfoPerObject ) { NSKeyValueObservationInfoPerObject = CFDictionaryCreateMutable (NULL , 0 , NULL , NULL ); } if (info) { CFDictionarySetValue (NSKeyValueObservationInfoPerObject , OBSERVATION_INFO_KEY(self ), info); } else { CFDictionaryRemoveValue (NSKeyValueObservationInfoPerObject , OBSERVATION_INFO_KEY(self )); } }
即这个方法的默认实现是以对象的指针作为key,从一个全局的字典中获取信息。由此,我们可以理解为,KVO的信息是存储在一个全局字典中,而不是存储在对象本身。 不过,为了提高效率,我们可以重写observationInfo属性的set和get方法,以将这个不透明的数据指针存储到一个实例变量中。但是,在重写时,我们不应该尝试去向这些数据发送一个Objective-C消息,包括retain和release。
3.新建与重写 在这一步骤中,动态创建了原来class的子类,当然,也重写和添加了许多方法。
3.1动态创建子类的核心实现 动态创建子类中,重写了dealloc、class、_isKVOA
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 NSKeyValueNotifyingInfo *_NSKVONotifyingCreateInfoWithOriginalClass(Class originalClass) { static IMP NSObjectWillChange ; static IMP NSObjectDidChange ; const char *originalClassName = class_getName(originalClass); size_t size = strlen(originalClassName) + 16 ; char *newClassName = (char *)malloc(size); strlcpy(newClassName, NOTIFY_CLASSNAME_PREFIX, size); strlcat(newClassName, originalClassName, size); Class newSubClass = objc_allocateClassPair(originalClass, newClassName, sizeof (NSKeyValueNotifyingInfo )); objc_registerClassPair(newSubClass); free(newClassName); unsigned char *ivars = object_getIndexedIvars(newSubClass); NSKeyValueNotifyingInfo *notifyingInfo = (NSKeyValueNotifyingInfo *)ivars; notifyingInfo->originalClass = originalClass; notifyingInfo->newSubClass = newSubClass; notifyingInfo->notifyingKeys = CFSetCreateMutable (NULL , 0 , &kCFCopyStringSetCallBacks); notifyingInfo->selKeyMap = CFDictionaryCreateMutable (NULL , 0 , NULL , &kCFTypeDictionaryValueCallBacks); pthread_mutexattr_t mutexattr; pthread_mutexattr_init(&mutexattr); pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(¬ifyingInfo->mutex, &mutexattr); pthread_mutexattr_destroy(&mutexattr); static dispatch_once_t NSObjectIMPLookupOnce ; dispatch_once (&NSObjectIMPLookupOnce , ^{ NSObjectWillChange = class_getMethodImplementation([NSObject class ], @selector (d_willChangeValueForKey:)); NSObjectDidChange = class_getMethodImplementation([NSObject class ], @selector (d_didChangeValueForKey:)); }); notifyingInfo->overrideWillOrDidChange = class_getMethodImplementation(notifyingInfo->originalClass, @selector (d_willChangeValueForKey:)) != NSObjectWillChange || class_getMethodImplementation(notifyingInfo->originalClass, @selector (d_didChangeValueForKey:)) != NSObjectDidChange ; NSKVONotifyingSetMethodImplementation (notifyingInfo, ISKVOA_SELECTOR, (IMP)NSKVOIsAutonotifying , NULL ); NSKVONotifyingSetMethodImplementation (notifyingInfo, @selector (dealloc), (IMP)NSKVODeallocate , NULL ); NSKVONotifyingSetMethodImplementation (notifyingInfo, @selector (class ), (IMP)NSKVOClass , NULL ); return notifyingInfo; }
添加_isKVOA方法 1 2 3 BOOL NSKVOIsAutonotifying () { return YES ; }
重写class方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Class NSKVOClass (id object , SEL selector ) { // 新的class : NSKVONotifying_XXXX Class currentClass = object_getClass (object ); // 原先的class : XXXX Class originalClass = _NSKVONotifyingOriginalClassForIsa (currentClass ); if (currentClass == originalClass) { // 相同, 返回object的currentClass Method m = class_getInstanceMethod(currentClass, selector); return ((Class (*)(id ,Method ))method_invoke )(object , m ); } else { // 不同, 返回originalClass return [originalClass class ]; } }
重写dealloc方法 获取object
对应的observationInfo
(对象)并把它放到结构体中,在调用完object
原先的dealloc
方法之后判断observationInfo
是否还存在,若存在说明observer
没有在dealloc
之前被移除掉,进而抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 void NSKVODeallocate (id object, SEL selector) { NSKeyValueObservationInfo *observationInfo = _NSKeyValueRetainedObservationInfoForObject(object, nil ); ObservationInfoWatcher watcher = {object, observationInfo, NULL }; _NSKeyValueAddObservationInfoWatcher(&watcher); NSKeyValueNotifyingInfo *notifyInfo = (NSKeyValueNotifyingInfo *)object_getIndexedIvars(object_getClass(object)); Method originDellocMethod = class_getInstanceMethod(notifyInfo->originalClass, selector); ((id (*)(id ,Method))method_invoke)(object, originDellocMethod); @try { if (watcher.observationInfo) { BOOL keyExistsAndHasValidFormat = false ; BOOL cleansUpBeforeThrowing = false ; cleansUpBeforeThrowing = (BOOL )CFPreferencesGetAppBooleanValue (CFSTR ("NSKVODeallocateCleansUpBeforeThrowing" ), kCFPreferencesCurrentApplication, (Boolean *)&keyExistsAndHasValidFormat); cleansUpBeforeThrowing = cleansUpBeforeThrowing && keyExistsAndHasValidFormat; if (dyld_get_program_sdk_version() > 0x7FFFF || cleansUpBeforeThrowing) { if (cleansUpBeforeThrowing) { _NSKeyValueRemoveObservationInfoForObject(object, watcher.observationInfo); } [NSException raise:NSInternalInconsistencyException format:@"An instance %p of class %@ was deallocated while key value observers were still registered with it. Current observation info: %@" , object, notifyInfo->originalClass, watcher.observationInfo]; } else { NSKVODeallocateBreak (object); } } } @catch (NSException *exception) { [exception raise]; } @finally { _NSKeyValueRemoveObservationInfoWatcher(&watcher); [watcher.observationInfo release]; } }
4.回调通知 上文分析,NSKeyValueNotifyObserver()
就是回调的函数,通过它调用observeValueForKeyPath:ofObject:change:context:
方法。这里还剩下最后一个问题,回调是怎么处理的?
在NSKVONotifyingEnableForInfoAndKey()
函数中重写setter方法,之后在NSSetPrimitiveValueAndNotify()
函数中先调用willChangeValueForKey
,再调用原先的setter方法,再调用didChangeValueForKey
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static inline void NSSetPrimitiveValueAndNotify(id object,SEL selector, void (^setValueWithImplementation)(IMP imp)) { NSKeyValueNotifyingInfo *info = object_getIndexedIvars(object_getClass(object)); pthread_mutex_lock (&info-> mutex); NSString *key = CFDictionaryGetValue(info-> selKeyMap, selector); key = [key copyWithZone:nil ]; pthread_mutex_unlock (&info-> mutex); if (info-> overrideWillOrDidChange) { [object willChangeValueForKey:key]; IMP imp = class_getMethodImplementation(info-> originalClass, selector); setValueWithImplementation(imp); [object didChangeValueForKey:key]; } else { [object changeValueForKey:key key:nil key:nil usingBlock:^{ IMP imp = class_getMethodImplementation(info-> originalClass, selector); setValueWithImplementation(imp); }]; } [key release]; }
当然,这里的didChangeValueForKey
也被重写实现了,它会调用真正的回调observeValueForKeyPath:ofObject:change:context:
方法。
五、移除观察者 找到NSKeyValueObservance移除即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 NSKeyValueObservationInfo *_NSKeyValueObservationInfoCreateByRemoving(NSKeyValueObservationInfo *baseObservationInfo, id observer, NSKeyValueProperty *property, void *context, BOOL shouldCompareContext, id originalObservable, BOOL *cacheHit, NSKeyValueObservance **removalObservance) { NSKeyValueObservationInfo *createdObservationInfo = nil ; NSUInteger observanceCount = CFArrayGetCount ((CFArrayRef )baseObservationInfo.observances); NSKeyValueObservance *observancesBuff[observanceCount]; CFArrayGetValues ((CFArrayRef )baseObservationInfo.observances, CFRangeMake (0 , observanceCount), (const void **)observancesBuff); NSUInteger removalObservanceIndex = NSNotFound ; for (NSInteger i = observanceCount - 1 ; i >= 0 ; --i) { NSKeyValueObservance *observance = observancesBuff[i]; if (observance.property == property && observance.observer == observer) { if (!shouldCompareContext || observance.context == context) { if (!originalObservable || observance.originalObservable == originalObservable) { *removalObservance = observance; removalObservanceIndex = i; break ; } } } } if (*removalObservance) { if (observanceCount > 1 ) { os_lock_lock(&NSKeyValueObservationInfoCreationSpinLock ); if (!NSKeyValueShareableObservationInfos ) { NSPointerFunctions *functions = [[NSPointerFunctions alloc] initWithOptions:NSPointerFunctionsWeakMemory ]; [functions setHashFunction:NSKeyValueShareableObservationInfoNSHTHash ]; [functions setIsEqualFunction:NSKeyValueShareableObservationInfoNSHTIsEqual ]; NSKeyValueShareableObservationInfos = [[NSHashTable alloc] initWithPointerFunctions:functions capacity:0 ]; [functions release]; } if (!NSKeyValueShareableObservationInfoKeyIsa ) { NSKeyValueShareableObservationInfoKeyIsa = NSKeyValueShareableObservationInfoKey .self; } static NSKeyValueShareableObservationInfoKey * shareableObservationInfoKey = nil ; if (!shareableObservationInfoKey) { shareableObservationInfoKey = [[NSKeyValueShareableObservationInfoKey alloc] init]; } shareableObservationInfoKey.addingNotRemoving = NO ; shareableObservationInfoKey.baseObservationInfo = baseObservationInfo; shareableObservationInfoKey.removalObservance = *removalObservance; shareableObservationInfoKey.removalObservanceIndex = removalObservanceIndex; shareableObservationInfoKey.cachedHash = NSKeyValueShareableObservationInfoNSHTHash (shareableObservationInfoKey, NULL ); NSKeyValueObservationInfo *existsObservationInfo = [NSKeyValueShareableObservationInfos member:shareableObservationInfoKey]; shareableObservationInfoKey.removalObservance = nil ; shareableObservationInfoKey.baseObservationInfo = nil ; NSUInteger cachedHash = shareableObservationInfoKey.cachedHash; shareableObservationInfoKey.cachedHash = 0 ; if (!existsObservationInfo) { memmove(observancesBuff + removalObservanceIndex, observancesBuff + removalObservanceIndex + 1 , (observanceCount - (removalObservanceIndex + 1 )) * sizeof (NSKeyValueObservance *)); createdObservationInfo = [[NSKeyValueObservationInfo alloc] _initWithObservances:observancesBuff count:observanceCount - 1 hashValue:cachedHash]; if (createdObservationInfo.cachedIsShareable) { [NSKeyValueShareableObservationInfos addObject:createdObservationInfo]; } *cacheHit = NO ; } else { *cacheHit = YES ; createdObservationInfo = [existsObservationInfo retain ]; } os_lock_unlock(&NSKeyValueObservationInfoCreationSpinLock ); return createdObservationInfo; } else { *cacheHit = YES ; } } return nil ; }
六、KVO自实现 有很多同学尝试自己实现了KVO,有按照原生接口的,也有自我发挥直接传递block的。由于之前我已经读过一些开源的代码,见《「KVOController」的封装》 ,作者就是使用了block很好地封装了KVO的回调。所以,这里还是试着按照原生接口实现一下。
由于对源码理解地不是十分透彻,再加上能力有限,在尝试实现过程中遇到不少问题,幸好都解决了。当然,代码肯定有不少问题的,而且仅仅实现一点核心功能,姑且当做玩具看看吧。
1.接口 1 2 3 4 5 6 7 8 9 @interface NSObject(YAKVO)@property void *ya_observationInfo;- (void )ya_willChangeValueForKey: (NSString *)key; - (void )ya_didChangeValueForKey: (NSString *)key; - (void )ya_addObserver: (NSObject *)observer forKeyPath: (NSString *)keyPath options: (NSKeyValueObservingOptions)options context: (void *)context; - (void )ya_removeObserver: (NSObject *)observer forKeyPath: (NSString *)keyPath context: (void *)context; - (void )ya_observeValueForKeyPath: (NSString *)keyPath ofObject: (id)object change: (NSDictionary<NSKeyValueChangeKey,id> *)change context: (void *)context; @end
2.实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 #define ClassPrefixCStr "YAKVONotifying_" // 新类的前缀 #define ClassPrefix @ ClassPrefixCStr #define OBSERVATION_INFO_KEY(object) ((void *)(~(NSUInteger)(object))) static NSMutableDictionary *YAKeyValueChangeDictionary = nil ;@interface NSObject (YAKVOPrivate )@end @implementation NSObject (YAKVOPrivate )- (BOOL )ya_isKVOClass { return NO ; } - (void )ya_changeValueForKey:(NSString *)key usingBlock:(void (^)(void ))block { [self ya_willChangeValueForKey:key]; if (block) block(); [self ya_didChangeValueForKey:key]; } @end @interface YAKeyValueProperty : NSObject @property (nonatomic , assign ) Class isaForAutonotifying;@property (nonatomic , copy ) NSString *keyPath;@property (nonatomic , assign ) Class originalClass;- (instancetype )initWithOriginalClass:(Class)originalClass keyPath:(NSString *)keyPath; @end @implementation YAKeyValueProperty - (instancetype )initWithOriginalClass:(Class)originalClass keyPath:(NSString *)keyPath { if (self = [super init]) { _originalClass = originalClass; _keyPath = keyPath; } return self ; } - (Class)isaForAutonotifying { const char *originalClassName = class_getName(_originalClass); size_t size = strlen(originalClassName) + 16 ; char *newClassName = (char *)malloc(size); strlcpy(newClassName, ClassPrefixCStr, size); strlcat(newClassName, originalClassName, size); Class newSubClass = objc_allocateClassPair(_originalClass, newClassName, 0 ); objc_registerClassPair(newSubClass); free(newClassName); NSString *uppercase= [[_keyPath substringToIndex:1 ] uppercaseString]; NSString *last = [_keyPath substringFromIndex:1 ]; NSString *setter = [NSString stringWithFormat:@"set%@%@:" , uppercase, last]; SEL sel = NSSelectorFromString (setter ); Method method = class_getInstanceMethod(newSubClass, sel); if (method) { const char *typeEncoding = method_getTypeEncoding(method); class_replaceMethod(newSubClass, sel, (IMP)YASetValueAndNotifyForKey, typeEncoding); } else { [[NSException exceptionWithName:@"缺少参数" reason:@"没有实现Setter方法" userInfo:nil ] raise]; } YAKVONotifyingSetMethodImplementation(newSubClass, @selector (ya_isKVOClass), (IMP)YAKVOIsAutonotifying); YAKVONotifyingSetMethodImplementation(newSubClass, @selector (class ), (IMP)YAKVOClass); return newSubClass; } BOOL YAKVOIsAutonotifying(id object, SEL sel) { return YES ; } Class YAKVOClass(id object, SEL sel) { Class currentClass = object_getClass(object); if ([object ya_isKVOClass]) { NSString *clsStr = [NSStringFromClass (currentClass) stringByReplacingOccurrencesOfString:ClassPrefix withString:@"" ]; return NSClassFromString (clsStr); } return currentClass; } void YASetValueAndNotifyForKey(id obj, SEL sel, id value, IMP imp) { NSString *key = [[NSStringFromSelector (sel) substringFromIndex:3 ] lowercaseString]; key = [key substringToIndex:key.length - 1 ]; [obj ya_changeValueForKey:key usingBlock:^{ Class cls = [obj class ]; IMP superImp = class_getMethodImplementation(cls, sel); ((void (*)(id ,SEL , id ))superImp)(obj, sel, value); }]; } void YAKVONotifyingSetMethodImplementation(Class cls, SEL sel, IMP imp) { Method originMethod = class_getInstanceMethod(cls, sel); const char *encoding = NULL ; if (originMethod) { encoding = method_getTypeEncoding(originMethod); class_addMethod(cls, sel, imp, encoding); } } @end @interface YAKeyValueObservance : NSObject @property (nonatomic , weak ) YAKeyValueProperty *property;@property (nonatomic , weak ) id observer;@property (nonatomic , assign ) void *context;@property (nonatomic , assign ) int options;- (instancetype )initWithObserver:(id )observer property:(YAKeyValueProperty *)property options:(int )options context:(void *)context; @end @implementation YAKeyValueObservance - (instancetype )initWithObserver:(id )observer property:(YAKeyValueProperty *)property options:(int )options context:(void *)context { if (self = [super init]) { _observer = observer; _property = property; _options = options; _context = context; } return self ; } - (NSUInteger )hash { NSUInteger observerContextHash = [[NSString stringWithFormat:@"%p-%p" , _observer, _context] hash]; return observerContextHash ^ _property.hash ^ _options; } - (BOOL )isEqual:(id )object { if (object == self ) return YES ; if (![object isKindOfClass:object_getClass(self )]) return NO ; YAKeyValueObservance *other = (YAKeyValueObservance *)object; return other.observer == self .observer && other.options == self .options && other.context == self .context; } @end @interface YAKeyValueObservationInfo : NSObject @property (nonatomic , strong ) NSArray <YAKeyValueObservance *> *observances;- (instancetype )initWithObservances:(NSArray <YAKeyValueObservance *> *)observances count:(NSUInteger )count; @end @implementation YAKeyValueObservationInfo - (instancetype )initWithObservances:(NSArray <YAKeyValueObservance *> *)observances count:(NSUInteger )count { if (self = [super init]) { _observances = [[NSArray alloc] initWithArray:observances]; } return self ; } @end @interface YAKeyValueObservationInfoKey : NSObject @property (nonatomic , strong ) YAKeyValueObservationInfo *baseObservationInfo;@property (nonatomic , strong ) NSObject *additionObserver;@property (nonatomic , strong ) YAKeyValueProperty *additionProperty;@property (nonatomic , assign ) NSUInteger additionOptions;@property (nonatomic , assign ) void * additionContext;@end @implementation YAKeyValueObservationInfoKey @end #pragma mark - Private methods BOOL YAKeyValuePropertyIsEqual(YAKeyValueProperty *property1, YAKeyValueProperty *property2) { return (property1.originalClass == property2.originalClass) && (property1.keyPath == property2.keyPath || [property1.keyPath isEqual: property2.keyPath]); } NSUInteger YAKeyValuePropertyHash(YAKeyValueProperty *property) { return property.keyPath.hash ^ (NSUInteger )(__bridge void *)property.originalClass; } static inline YAKeyValueProperty *getKeyValueProperty(Class cls, NSString *keyPath) { static CFMutableSetRef YAKeyValueProperties; if (!YAKeyValueProperties) { CFSetCallBacks callbacks = {0 }; callbacks.version = kCFTypeSetCallBacks.version; callbacks.retain = kCFTypeSetCallBacks.retain; callbacks.release = kCFTypeSetCallBacks.release; callbacks.copyDescription = kCFTypeSetCallBacks.copyDescription; callbacks.equal = (CFSetEqualCallBack )YAKeyValuePropertyIsEqual; callbacks.hash = (CFSetHashCallBack )YAKeyValuePropertyHash; YAKeyValueProperties = CFSetCreateMutable (NULL , 0 , &callbacks); } static YAKeyValueProperty *finder; if (!finder) finder = [YAKeyValueProperty new]; finder.originalClass = cls; finder.keyPath = keyPath; YAKeyValueProperty *property = CFSetGetValue (YAKeyValueProperties, (__bridge const void *)(finder)); if (!property) { property = [[YAKeyValueProperty alloc] initWithOriginalClass:cls keyPath:keyPath]; CFSetAddValue (YAKeyValueProperties, (__bridge const void *)(property)); } return property; } static inline YAKeyValueObservance *getKeyValueObservance(YAKeyValueProperty *property, id observer, void *context, int options) { static NSHashTable *YAKeyValueShareableObservances; if (!YAKeyValueShareableObservances) { YAKeyValueShareableObservances = [NSHashTable weakObjectsHashTable]; } static YAKeyValueObservance *finder; if (!finder) finder = [YAKeyValueObservance new]; finder.property = property; finder.context = context; finder.observer = observer; finder.options = options; YAKeyValueObservance *observance = [YAKeyValueShareableObservances member:finder]; if (!observance) { observance = [[YAKeyValueObservance alloc] initWithObserver:observer property:property options:options context:context]; [YAKeyValueShareableObservances addObject:observance]; } return observance; } NSUInteger YAKeyValueObservationInfoNSHTHash(const void *item, NSUInteger (*size)(const void *item)) { if (object_getClass((__bridge id )item) == YAKeyValueObservationInfoKey.class) { YAKeyValueObservationInfoKey *key = (__bridge YAKeyValueObservationInfoKey *)item; return key.baseObservationInfo.observances.firstObject.hash; } else { YAKeyValueObservationInfo *info = (__bridge YAKeyValueObservationInfo *)item; return info.observances.firstObject.hash; } } BOOL YAKeyValueObservationInfoNSHTIsEqual(const void *item1, const void *item2, NSUInteger (* size)(const void * item)) { if (object_getClass((__bridge id )item1) == YAKeyValueObservationInfoKey.class || object_getClass((__bridge id )item2) == YAKeyValueObservationInfoKey.class) { YAKeyValueObservationInfo *info = nil ; YAKeyValueObservationInfoKey *key = nil ; if (object_getClass((__bridge id )item1) == YAKeyValueObservationInfoKey.class) { info = (__bridge YAKeyValueObservationInfo *)item2; key = (__bridge YAKeyValueObservationInfoKey *)item1; } else { info = (__bridge YAKeyValueObservationInfo *)item1; key = (__bridge YAKeyValueObservationInfoKey *)item2; } NSArray <YAKeyValueObservance *> *observancesInKey = key.baseObservationInfo.observances; NSArray <YAKeyValueObservance *> *observancesInInfo = info.observances; NSUInteger countInkey = observancesInKey.count; NSUInteger countInInfo = observancesInInfo.count; if (countInkey != countInInfo) return NO ; for (NSUInteger i = 0 ; i < countInkey; i++) { if (observancesInKey[i] != observancesInInfo[i]) { return NO ; } } return YES ; } return NO ; } #pragma mark - Public methods @implementation NSObject (YAKVO )CFMutableDictionaryRef YAKeyValueObservationInfoPerObject = NULL ;- (void *)ya_observationInfo { return YAKeyValueObservationInfoPerObject ? (void *)CFDictionaryGetValue (YAKeyValueObservationInfoPerObject, OBSERVATION_INFO_KEY(self )) : NULL ; } - (void )setYa_observationInfo:(void *)info { if (!YAKeyValueObservationInfoPerObject) { CFDictionaryValueCallBacks callbacks = {0 }; callbacks.version = kCFTypeDictionaryKeyCallBacks.version; callbacks.retain = kCFTypeDictionaryKeyCallBacks.retain; callbacks.release = kCFTypeDictionaryKeyCallBacks.release; callbacks.copyDescription = kCFTypeDictionaryKeyCallBacks.copyDescription; YAKeyValueObservationInfoPerObject = CFDictionaryCreateMutable (NULL , 0 , NULL , &callbacks); } if (info) { CFDictionarySetValue (YAKeyValueObservationInfoPerObject, OBSERVATION_INFO_KEY(self ), info); } else { CFDictionaryRemoveValue (YAKeyValueObservationInfoPerObject, OBSERVATION_INFO_KEY(self )); } } - (void )ya_willChangeValueForKey:(NSString *)key { if (!YAKeyValueChangeDictionary) { YAKeyValueChangeDictionary = [NSMutableDictionary dictionary]; } id oldValue = nil ; oldValue = [self valueForKeyPath:key]; if (!oldValue) oldValue = [NSNull null]; [YAKeyValueChangeDictionary setObject:oldValue forKey:[NSString stringWithFormat:@"%p-old" , self ]]; } - (void )ya_didChangeValueForKey:(NSString *)key { if (self .ya_isKVOClass) { YAKeyValueProperty *property = getKeyValueProperty(self .class, key); YAKeyValueObservationInfo *observation = self .ya_observationInfo; [observation.observances enumerateObjectsUsingBlock:^(YAKeyValueObservance *obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj.property isEqual:property]) { NSMutableDictionary *change = [NSMutableDictionary dictionary]; if (obj.options & NSKeyValueObservingOptionOld ) { id old = [YAKeyValueChangeDictionary objectForKey:[NSString stringWithFormat:@"%p-old" , self ]]; [change setObject:old forKey:@"old" ]; } else { [YAKeyValueChangeDictionary removeObjectForKey:[NSString stringWithFormat:@"%p-old" , self ]]; } if (obj.options & NSKeyValueObservingOptionNew ) { id newValue = nil ; newValue = [self valueForKeyPath:key]; if (!newValue) newValue = [NSNull null]; [YAKeyValueChangeDictionary setObject:newValue forKey:[NSString stringWithFormat:@"%p-new" , self ]]; [change setObject:newValue forKey:@"new" ]; } [obj.observer ya_observeValueForKeyPath:key ofObject:self change:change context:nil ]; } }]; } } - (void )ya_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions )options context:(void *)context { YAKeyValueProperty *property = getKeyValueProperty(self .class, keyPath); YAKeyValueObservance *observance = getKeyValueObservance(property, observer, context, options); static NSHashTable *YAKeyValueShareableObservationInfos; if (!YAKeyValueShareableObservationInfos) { NSPointerFunctions *pointerFunctions = [[NSPointerFunctions alloc] initWithOptions:NSPointerFunctionsWeakMemory ]; [pointerFunctions setHashFunction:YAKeyValueObservationInfoNSHTHash]; [pointerFunctions setIsEqualFunction:YAKeyValueObservationInfoNSHTIsEqual]; YAKeyValueShareableObservationInfos = [[NSHashTable alloc] initWithPointerFunctions:pointerFunctions capacity:0 ]; } static YAKeyValueObservationInfoKey *finder; if (!finder) { finder = [YAKeyValueObservationInfoKey new]; } YAKeyValueObservationInfo *info = (__bridge id )[self ya_observationInfo]; finder.baseObservationInfo = info; finder.additionObserver = observer; finder.additionContext = context; finder.additionOptions = options; finder.additionProperty = property; YAKeyValueObservationInfo *observation = [YAKeyValueShareableObservationInfos member:finder]; finder.baseObservationInfo = nil ; finder.additionObserver = nil ; finder.additionContext = NULL ; finder.additionOptions = 0 ; finder.additionProperty = nil ; if (!observation) { observation = [[YAKeyValueObservationInfo alloc] initWithObservances:@[observance] count:1 ]; [YAKeyValueShareableObservationInfos addObject:observation]; } else { NSMutableArray *buffer = [NSMutableArray arrayWithArray:observation.observances]; [buffer addObject:observance]; observation.observances = [NSArray arrayWithArray:buffer]; } self .ya_observationInfo = (__bridge void *)(observation); if (!self .ya_isKVOClass) { Class isaForAutonotifying = [property isaForAutonotifying]; object_setClass(self , isaForAutonotifying); } if (options & NSKeyValueObservingOptionInitial ) { id newValue = nil ; if (options & NSKeyValueObservingOptionNew ) { newValue = [self valueForKeyPath:keyPath]; } if (!newValue) newValue = [NSNull null]; NSDictionary *change = @{@"new" : newValue}; [observer ya_observeValueForKeyPath:keyPath ofObject:self change:change context:context]; } } - (void )ya_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context { if (self .ya_isKVOClass) { YAKeyValueProperty *property = getKeyValueProperty(self .class, keyPath); YAKeyValueObservationInfo *observation = self .ya_observationInfo; NSMutableArray *diff = [NSMutableArray arrayWithArray:observation.observances]; __block NSInteger removeIdx = -1 ; [diff enumerateObjectsUsingBlock:^(YAKeyValueObservance *obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj.property isEqual:property] && obj.observer == observer && obj.context == context) { removeIdx = idx; *stop = YES ; } }]; if (removeIdx != -1 ) { [diff removeObjectAtIndex:removeIdx]; observation.observances = [NSArray arrayWithArray:diff]; } } } - (void )ya_observeValueForKeyPath:(NSString *)keyPath ofObject:(id )object change:(NSDictionary <NSKeyValueChangeKey ,id > *)change context:(void *)context{} @end
功能check 1.添加观察者与设置回调:
1 2 3 4 5 6 7 [self .obj ya_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:"NULL" ]; - (void )ya_observeValueForKeyPath:(NSString *)keyPath ofObject:(id )object change:(NSDictionary <NSKeyValueChangeKey ,id > *)change context:(void *)context { NSLog (@"%@" , change); } self .obj.name = @"Aaron" ;self .obj.name = @"Jack" ;
打印:
1 2 3 4 5 6 7 8 2019-05 -30 09:41:23.595046+0800 Aaron[24893:604622] { new = Aaron; old = "<null>"; } 2019-05 -30 09:41:23.595215+0800 Aaron[24893:604622] { new = Jack; old = Aaron; }
2.使用NSKeyValueObservingOptionInitial
1 2 [self.obj ya_addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionInitial context: "NULL" ]; self.obj.name = @"Aaron" ;
打印1 2 3 2019 -05 -30 09 :45 :01.717131 +0800 Aaron[25010 :609398 ] { new = "<null>" ; }
3.多次添加观察者
1 2 3 4 [self.obj ya_addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: "NULL" ]; [self.obj ya_addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: "NULL" ]; [self.obj ya_addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: "NULL" ]; self.obj.name = @"Aaron" ;
打印
1 2 3 4 5 6 7 8 9 2019-05 -30 09:47:20.625826+0800 Aaron[25107:613531] { new = Aaron; } 2019-05 -30 09:47:20.625992+0800 Aaron[25107:613531] { new = Aaron; } 2019-05 -30 09:47:20.626128+0800 Aaron[25107:613531] { new = Aaron; }
具体的代码放到了github上:https://github.com/ChenYalun/Project/tree/master/KVO
七、小结 这里学到一个技巧:如何确认NSUserDefaults
中某个key是否存在?
比如
1 BOOL result = [NSUserDefaults.standardUserDefaults boolForKey:@"key" ];
当result为NO时,怎么判断是存储键@"key"
对应的value是NO,还是说压根就没有存过这个key呢?可以使用CFPreferencesGetAppBooleanValue()
函数。 KVO中有这么一段代码:
1 2 3 4 5 6 BOOL keyExistsAndHasValidFormat = false ; BOOL cleansUpBeforeThrowing = false ; cleansUpBeforeThrowing = (BOOL )CFPreferencesGetAppBooleanValue (CFSTR ("key" ), kCFPreferencesCurrentApplication, (Boolean *)&keyExistsAndHasValidFormat); cleansUpBeforeThrowing = cleansUpBeforeThrowing && keyExistsAndHasValidFormat;
好了,说正题,本文简单总结了KVO的原理,并尝试阅读源码,然而能力有限、时间有限,只能明白个大概。在应用场景方面,就是“观察”,实际开发中,监听UIScrollView(及其子类)的属性比较多。最后,给自己挖了个坑:willChangeValueForKey:
和didChangeValueForKey:
为什么要成对出现?哪天有时间了,再仔细看一看。
KVO的源码来自:https://github.com/renjinkui2719/DIS_KVC_KVO 。感谢作者。
参考文章Foundation: NSKeyValueObserving(KVO) 如何自己动手实现 KVO 使用Block实现KVO objc kvo简单探索 KVO 原理详解