以前寫了一篇kvo原理探索,今天咱們來本身動手實現它的邏輯,這樣咱們對kvo
的認識會更深入一些。git
咱們根據系統kvo
的使用,咱們須要定義如下接口:github
- 添加觀察者
- 接收通知回調
- 移除觀察者
複製代碼
可是爲了更加完善一些,咱們又添加了automatically
方法、ss_willChangeValueForKey:
和 ss_didChangeValueForKey:
等方法,真正實現手自一體
。 web
根據系統KVO
的相關接口,咱們也給NSObject
添加一個Category
,具體的頭文件咱們定義以下:編輯器
typedef NS_OPTIONS(NSUInteger, SSKeyValueObservingOptions) {
SSKeyValueObservingOptionNew = 0x01,
SSKeyValueObservingOptionOld = 0x02,
};
@interface NSObject (SSKVO)
/** Register an observer of the value at a key path relative to the receiver */
- (void)ss_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(SSKeyValueObservingOptions)options context:(nullable void *)context;
/* deregister as an observer of the value at a key path relative to the receiver*/
- (void)ss_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
/* Given that the receiver has been registered as an observer of the value at a key path relative to an object, be notified of a change to that value */
- (void)ss_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
+ (BOOL)ss_automaticallyNotifiesObserversForKey:(NSString *)key;
- (void)ss_willChangeValueForKey:(NSString *)key;
- (void)ss_didChangeValueForKey:(NSString *)key;
@end
複製代碼
接口定義完了以後,咱們就針對定義的接口方法,一一去實現邏輯:post
因爲咱們須要保存觀察者的相關信息以便於在後續的設值和傳值更加方便,此處咱們封裝一個SSKVOInfo
的對象,考慮到一個對象有多個屬性值可能會被觀察,咱們建立一個以keyPath
做爲鍵值的一個字典,保存了多有keyPath
的觀察者信息。測試
@interface SSKVOInfo : NSObject {
@public
id observer;
void *context;
int options;
NSString *keyPath;
NSDictionary *changes;
}
@end
@implementation SSKVOInfo
@end
複製代碼
那麼接下來咱們一一針對咱們的接口去實現:ui
- (void)ss_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(SSKeyValueObservingOptions)options context:(nullable void *)context {
// 動態生成子類
Class replacement = [self replacementClass];
// 添加setter方法
{
SEL sel = NSSelectorFromString(setterForGetter(keyPath));
Method method = class_getInstanceMethod(self.class, sel);
const char *types = method_getTypeEncoding(method);
/** 重寫setter方法 */
class_addMethod(replacement , sel, (IMP)ss_setter, types);
}
SSKVOInfo *kvoInfo = [SSKVOInfo new];
kvoInfo->context = context;
kvoInfo->options = options;
kvoInfo->keyPath = keyPath;
kvoInfo->observer = observer;
kvoInfo->changes = @{};
// 添加信息
[self addKVOInfo:kvoInfo forKey:keyPath];
}
複製代碼
SSKVOInfo
相關方法以下:spa
- (SSKVOInfo *)kvoInfoForKey:(NSString *)key {
NSMutableDictionary *observerInfo = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(SSKVOAssiociateKey));
id object = [observerInfo objectForKey:key];
if (object && [object isKindOfClass:[SSKVOInfo class]]) {
return object;
}
return nil;
}
- (void)addKVOInfo:(SSKVOInfo *)kvoInfo forKey:(NSString *)key {
NSMutableDictionary *observerInfo = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(SSKVOAssiociateKey));
if (!observerInfo) {
observerInfo = [NSMutableDictionary dictionary];
}
[observerInfo setObject:kvoInfo forKey:key];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(SSKVOAssiociateKey), observerInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
複製代碼
何時發送通知呢???調試
/** 重寫set方法 */
void ss_setter(id self, SEL _cmd, id value)
{
NSString *v = NSStringFromSelector(_cmd);
NSString *key = getterForSetter(v);
Class c = [self class];
/** 自動觀察,咱們自動調用ss_willChangeValueForKey 和 ss_didChangeValueForKey*/
if ([c ss_automaticallyNotifiesObserversForKey:key]) {
[self ss_willChangeValueForKey:key];
/** 調用父類的set方法 */
ss_sendSuper(self, _cmd, value);
[self ss_didChangeValueForKey:key];
} else {// 只調用父類方法,若是父類本身實現will和did方法,也會發送通知
ss_sendSuper(self, _cmd, value);
}
}
複製代碼
其關聯的手動KVO
實現以下:code
/** 調用父類的set方法 */
void ss_sendSuper(id self, SEL _cmd, id value)
{
void (*ss_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
//objc_msgSendSuper(&superStruct,_cmd,newValue)
ss_msgSendSuper(&superStruct,_cmd,value);
}
- (void)ss_willChangeValueForKey:(NSString *)key {
[self saveCurrentValueFor:SSKVO_Old keyPath:key];
}
- (void)ss_didChangeValueForKey:(NSString *)key {
[self saveCurrentValueFor:SSKVO_New keyPath:key];
[self sendNotification:key];
}
- (void)saveCurrentValueFor:(NSString *)changeKey keyPath:(NSString *)keyPath {
id object = [self kvoInfoForKey:keyPath];
if (!object || ![object isKindOfClass:[SSKVOInfo class]]) {return;}
SSKVOInfo *kvoInfo = (SSKVOInfo *)object;
NSMutableDictionary *changes = kvoInfo->changes.mutableCopy;
id value = [self valueForKey:keyPath]?[self valueForKey:keyPath]:@"";
[changes setObject:value forKey:changeKey];
kvoInfo->changes = changes.copy;
}
- (void)sendNotification:(NSString *)keyPath {
id object = [self kvoInfoForKey:keyPath];
if (!object || ![object isKindOfClass:[SSKVOInfo class]]) {return;}
SSKVOInfo *kvoInfo = (SSKVOInfo *)object;
// send notification
id observer = kvoInfo->observer;
// 發送通知
if (observer && [observer respondsToSelector:@selector(ss_observeValueForKeyPath:ofObject:change:context:)]) {
int options = kvoInfo->options;
NSDictionary *changes = kvoInfo->changes;
NSMutableDictionary *info = [NSMutableDictionary dictionaryWithCapacity:2];
if (options & SSKeyValueObservingOptionNew) {
[info setObject:changes[SSKVO_New] forKey:SSKVO_New];
}
if (options & SSKeyValueObservingOptionOld) {
[info setObject:changes[SSKVO_Old] forKey:SSKVO_Old];
}
[observer ss_observeValueForKeyPath:keyPath ofObject:self change:info context:kvoInfo->context];
}
}
複製代碼
- (void)ss_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context {
[self removeKVOInfoForKey:keyPath];
}
- (void)removeKVOInfoForKey:(NSString *)key {
NSMutableDictionary *observerInfo = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(SSKVOAssiociateKey));
[observerInfo removeObjectForKey:key];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(SSKVOAssiociateKey), observerInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (observerInfo.count<=0) {
// 指回給父類
Class superClass = [self class];
object_setClass(self, superClass);
}
}
複製代碼
代碼邏輯實現了,必不可少的就是測試了:
這是自動擋,接下來咱們試試手動擋如何,調整SSPerson
的實現以下:
#import "SSPerson.h"
#import "NSObject+SSKVO.h"
@implementation SSPerson
+ (BOOL)ss_automaticallyNotifiesObserversForKey:(NSString *)key {
return NO;
}
- (void)setName:(NSString *)name {
[self ss_willChangeValueForKey:NSStringFromSelector(@selector(name))];
_name = @"here is custom value";
[self ss_didChangeValueForKey:NSStringFromSelector(@selector(name))];
}
@end
複製代碼
運行一樣點擊兩次屏幕咱們看看打印結果是否爲here is custom value
至此咱們大概完成了自定義kvo
的實現,上述只展現了一些主要的核心邏輯,具體的代碼須要的話請前往SSKVO-demo下載,若是說這份代碼不夠縝密,那是固然的,好比咱們只處理了對象類型的屬性觀察缺乏了基本類型的處理,好比咱們的keyPath
只是針對key
並無path
的處理,還有針對KVC
是如何觸發KVO
的實現等等,這些後續我都會去完善,也但願您能多多賜教和點評。最後,看完別忘了...