博客連接KVO實現原理bash
在iOS開發中,咱們能夠經過KVO機制來監聽某個對象的某個屬性的變化。併發
KVO的實現分爲三步:app
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
async
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
ide
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
函數
KVO的實現依賴於RunTime,在Apple的文檔中有提到過KVO的實現:ui
Automatic key-value observing is implemented using a technique called isa-swizzling.atom
The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.spa
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.線程
You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.
Apple的文檔提到KVO是使用了isa-swizzling
的技術。當觀察者註冊對象的屬性時,觀察對象的isa
指針被修改,指向中間類而不是真正的類。所以,isa
指針的值不必定反映實例的實際類。另外還提到咱們不該該依賴isa
指針來肯定類成員資格,而是使用類方法來肯定對象實例的類。
先用一段代碼驗證一下KVO的實現是否是進行了isa-swizzling
:
@interface KVOTestModel : NSObject
@property (nonatomic, copy) NSString *name;
- (void)printInfo;
@end
@implementation KVOTestModel
- (void)printInfo {
NSLog(@"isa:%@, supper class:%@", NSStringFromClass(object_getClass(self)), class_getSuperclass(object_getClass(self)));
NSLog(@"self:%@, [self superclass]:%@", self, [self superclass]);
NSLog(@"name setter function pointer:%p", class_getMethodImplementation(object_getClass(self), @selector(setName:)));
NSLog(@"printInfo function pointer:%p", class_getMethodImplementation(object_getClass(self), @selector(printInfo)));
}
@end
複製代碼
在ViewController
中使用KVO監聽KVOTestMod
對象的相關屬性:
#pragma mark - Lifceycle
- (void)viewDidLoad {
[super viewDidLoad];
self.kvoTestModel = [[KVOTestModel alloc] init];
NSLog(@"Before KVO ---------------------------------------");
[self.kvoTestModel printInfo];
[self.kvoTestModel addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew
context:nil];
NSLog(@"After KVO ---------------------------------------");
[self.kvoTestModel printInfo];
[self.kvoTestModel removeObserver:self forKeyPath:@"name"];
NSLog(@"Remove KVO ---------------------------------------");
[self.kvoTestModel printInfo];
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context {
}
複製代碼
打印一下結果:
添加KVO以後,isa
已經替換成了NSKVONotifying_Person
,而根據class_getSuperclass
獲得的結果居然是Person
, 而後name
是使咱們KVO須要觀察的屬性,它的setter
函數指針變了。
這裏先直接總結KVO的實現原理:
add observer
NSKVONotifying_
+類名
的形式來命名的派生類;isa
指針指向這個派生類;setter
方法,重寫setter
方法的本質是在賦值語句以前調用willChangeValueForKey
,賦值以後調用didChangeValueForKey
,在didChangeValueForKey
中調用observeValueForKeyPath:ofObject:change:context:
方法。remove observer
將其的isa
指針指向原來的類對象中
在使用了KVO添加了觀察者之後,runtime會生成一個NSKVONotifying_
開頭的派生類,那這個類作了些什麼呢?驗證代碼以下:
// KVOTestModel類
@interface KVOTestModel : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation KVOTestModel
@end
// 打印類的方法列表
void printClassMethod(Class aClass) {
unsigned int count;
Method *methodList = class_copyMethodList(aClass, &count);
NSMutableString *methodNames = [NSMutableString string];
for (int i = 0; i < count; i++) {
Method aMethod =methodList[i];
NSString *aMethodString = NSStringFromSelector(method_getName(aMethod));
[methodNames appendString:[NSString stringWithFormat:@"%@, ", aMethodString]];
}
free(methodList);
NSLog(@"%@", methodNames);
}
//調用代碼
- (void)viewDidLoad {
[super viewDidLoad];
self.kvoTestModel = [[KVOTestModel alloc] init];
self.kvoTestModel2 = [[KVOTestModel alloc] init];
[self.kvoTestModel addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew
context:nil];
printClassMethod(object_getClass(self.kvoTestModel));
printClassMethod(object_getClass(self.kvoTestModel2));
}
複製代碼
執行結果以下:
從上面的結果能夠得出,派生類重寫了四個方法分別是:
class
方法和object_getClass
方法可是結果不同;上面提到必須是派生類重寫setter
方法,若是是直接對屬性進行賦值的話,是不會觸發KVO的。雖然Apple並無開源KVO的代碼,可是咱們能夠經過驗證的方式進行推導。
在KVOTestModel
文件中添加如下代碼:
#pragma mark - Override
- (void)willChangeValueForKey:(NSString *)key {
NSLog(@"willChangeValueForKey beigin");
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey end");
}
- (void)didChangeValueForKey:(NSString *)key {
NSLog(@"didChangeValueForKey beigin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey end");
}
#pragma mark - Setter
- (void)setName:(NSString *)name {
_name = name;
NSLog(@"%s", __func__);
}
複製代碼
打印結果:
從上面的結果咱們能夠知道KVO在重寫setter
方法後大概分紅三個步驟:
willChangeValueForKey
;setter
方法;didChangeValueForKey
,didChangeValueForKey
中會調用KVO的相關代理方法來通知觀察者。keyPath使用咱們用來監聽的屬性,它的實質是什麼?先看一段代碼:
//Person.h
@interface Person : NSObject
@property (nonatomic, copy) NSString *nick;
@end
//Person.m
@synthesize nick = realNick;
- (void)setNick:(NSString *)nick {
realNick = nick;
}
- (NSString *)nick {
return realNick;
}
//ViewController
- (void)_testKeyPath {
self.person = [[Person alloc] init];
[self.person addObserver:self
forKeyPath:@"nick"//"realNick"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
self.person.nick = @"Nero";
[self.person removeObserver:self forKeyPath:@"nick"];
}
複製代碼
實際結果是,使用nick
可以監聽到對應變化,而使用真正的實例變量realNick
時,沒法監聽到值。keyPath
指向的並非真正的實例變量,而是對於setter
方法的關聯,KVO會使用keypath
做爲後綴去尋找原類的setter
方法的方法簽名,和實際存取對象和屬性名稱沒有關係。因此這也是爲何咱們重命名了setter
方法以後,沒有辦法再去使用KVO或KVC了,須要手動調用一次willChangeValue
方法。
代碼以下:
//Person
@interface Person : NSObject {
@public NSInteger age;
}
@end
//ViewController
- (void)_testImplementKVOManually {
self.person = [[Person alloc] init];
[self.person addObserver:self
forKeyPath:@"age"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
[self.person willChangeValueForKey:@"age"];//獲取舊值
self.person->age = 26;
[self.person didChangeValueForKey:@"age"];//獲取新值
[self.person removeObserver:self forKeyPath:@"age"];
}
複製代碼
咱們知道使用Notification時,跨線程發送通知是沒法被接受到的,可是KVO是能夠跨線程監聽的。
- (void)_testKVOinGCD {
dispatch_queue_t queue = dispatch_queue_create("test.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
self.person = [Person new];
dispatch_async(queue, ^{
NSLog(@"%@", [NSDate date]);
self.person.name = @"Nero";
});
dispatch_async(queue, ^{
NSLog(@"%@", [NSDate date]);
sleep(1);
[self.person addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
NSLog(@"%@", [NSDate date]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"%@", [NSDate date]);
self.person.name = @"NeroXie";
});
}
複製代碼
能夠看到在兩個不一樣的線程裏建立的Observer和Target,觀察變化也是可以生效的。 這裏使用了dispatch_barrier_async
是確保第三個task在前兩個task運行後再執行,並且使用的隊列必須是自定義的併發隊列,若是使用全局隊列,柵欄就不想起做用了,由於dispatch_barrier_async
至關於dispatch_asysc
由於繼承的關係Father <- Son <- KVOSon,當我監聽一個父類屬性的keyPath的時候,Son實例一樣能夠經過消息查找找到父類的setter方法,再將該方法加入到KVOSon類當中去。
在上一條中知道,其實子類監聽父類屬性,並不依賴繼承,而是經過ISA指針在消息轉發的時候可以獲取到父類方法就足夠。因此當咱們重寫父類setter方法,至關於在子類定義了該setter函數,在咱們去用sel找方法簽名時,直接在子類中就拿到了,甚至都不須要去到父類裏。因此理解了KVO監聽父類屬性和繼承沒有直接聯繫這一點,就再也不糾結set方法是否重寫這個問題了。
經過上面的API咱們知道KVO的調用相對來講是有點繁瑣的,因此我用Category實現了KVO的Block形式的調用
// 聲明
@interface NSObject(KVOBlock)
- (NSString *)nn_addObserverForKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
usingBlock:(void (^)(id obj, NSString *keyPath, NSDictionary *change))block;
- (void)nn_removeBlockObserverWithIdentifier:(NSString *)identifier;
- (void)nn_removeAllBlockObservers;
@end
// 實現
#pragma mark - NSObject+KVOBlock
static void *NNObserverBlockContext = &NNObserverBlockContext;
typedef void (^NNObserverBlock) (id obj, NSString *keyPath, NSDictionary<NSKeyValueChangeKey,id> *change);
#pragma mark - Private Class
@interface _NNInternalObserver : NSObject
@property (nonatomic, assign) BOOL isObserving;
@property (nonatomic, weak) id observed;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, copy) NNObserverBlock observerBlock;
@end
@implementation _NNInternalObserver
#pragma mark - Init
- (instancetype)initWithObserved:(id)observed
keyPath:(NSString *)keyPath
observerBlock:(NNObserverBlock)observerBlock {
if ((self = [super init])) {
self.isObserving = NO;
self.observed = observed;
self.keyPath = keyPath;
self.observerBlock = [observerBlock copy];
}
return self;
}
- (void)dealloc {
if (self.keyPath) [self stopObserving];
}
#pragma mark - Helper
- (void)startObservingWithOptions:(NSKeyValueObservingOptions)options {
@synchronized(self) {
if (self.isObserving) return;
[self.observed addObserver:self forKeyPath:self.keyPath options:options context:NNObserverBlockContext];
self.isObserving = YES;
}
}
- (void)stopObserving {
NSParameterAssert(self.keyPath);
@synchronized (self) {
if (!self.isObserving) return;
if (!self.observed) return;
[self.observed removeObserver:self forKeyPath:self.keyPath context:NNObserverBlockContext];
self.observed = nil;
self.keyPath = nil;
self.observerBlock = nil;
}
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context != NNObserverBlockContext) return;
@synchronized (self) {
self.observerBlock(object, keyPath, change);
}
}
@end
@interface NSObject()
/** 保存全部的block */
@property (nonatomic, strong, setter=nn_setObserverBlockMap:) NSMutableDictionary *nn_observerBlockMap;
@end
@implementation NSObject(KVOBlock)
/** 全部修改過dealloc方法的類 */
+ (NSMutableSet *)nn_observedClasses {
static dispatch_once_t onceToken;
static NSMutableSet *classes = nil;
dispatch_once(&onceToken, ^{
classes = [[NSMutableSet alloc] init];
});
return classes;
}
#pragma mark - Public
- (NSString *)nn_addObserverForKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
usingBlock:(void (^)(id obj, NSString *keyPath, NSDictionary *change))block {
NSString *identifier = [NSProcessInfo processInfo].globallyUniqueString;
[self nn_addObserverForKeyPath:keyPath identifier:identifier options:options block:block];
return identifier;
}
- (void)nn_removeBlockObserverWithIdentifier:(NSString *)identifier {
NSParameterAssert(identifier.length);
NSMutableDictionary *dict;
@synchronized (self) {
dict = self.nn_observerBlockMap;
if (!dict) return;
}
_NNInternalObserver *observer = dict[identifier];
[observer stopObserving];
[dict removeObjectForKey:identifier];
if (dict.count == 0) self.nn_observerBlockMap = nil;
}
- (void)nn_removeAllBlockObservers {
NSDictionary *dict;
@synchronized (self) {
dict = [self.nn_observerBlockMap copy];
self.nn_observerBlockMap = nil;
}
[dict.allValues enumerateObjectsUsingBlock:^(_NNInternalObserver *obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj stopObserving];
}];
}
#pragma mark - Core Method
/**
KVO Block 實現
@param keyPath 被觀察的屬性
@param identifier 惟一標識符 用來標記內部觀察者
@param options 觀察選項
@param block KVO Block
*/
- (void)nn_addObserverForKeyPath:(NSString *)keyPath
identifier:(NSString *)identifier
options:(NSKeyValueObservingOptions)options
block:(NNObserverBlock)block {
NSParameterAssert(keyPath.length);
NSParameterAssert(identifier.length);
NSParameterAssert(block);
Class classToSwizzle = self.class;
NSMutableSet *classes = self.class.nn_observedClasses;
@synchronized (classes) {
NSString *className = NSStringFromClass(classToSwizzle);
if (![classes containsObject:className]) {
SEL deallocSelector = sel_registerName("dealloc");
__block void (*originalDealloc)(__unsafe_unretained id, SEL) = NULL;
id newDealloc = ^(__unsafe_unretained id objSelf) {
[objSelf nn_removeAllBlockObservers];
if (originalDealloc == NULL) {
struct objc_super superInfo = {
.receiver = objSelf,
.super_class = class_getSuperclass(classToSwizzle)
};
void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper;
msgSend(&superInfo, deallocSelector);
} else {
originalDealloc(objSelf, deallocSelector);
}
};
IMP newDeallocIMP = imp_implementationWithBlock(newDealloc);
if (!class_addMethod(classToSwizzle, deallocSelector, newDeallocIMP, "v@:")) {
Method deallocMethod = class_getInstanceMethod(classToSwizzle, deallocSelector);
originalDealloc = (void(*)(__unsafe_unretained id, SEL))method_getImplementation(deallocMethod);
originalDealloc = (void(*)(__unsafe_unretained id, SEL))method_setImplementation(deallocMethod, newDeallocIMP);
}
[classes addObject:className];
}
}
_NNInternalObserver *observer = [[_NNInternalObserver alloc] initWithObserved:self
keyPath:keyPath
observerBlock:block];
[observer startObservingWithOptions:options];
@synchronized (self) {
if (!self.nn_observerBlockMap) self.nn_observerBlockMap = [NSMutableDictionary dictionary];
}
self.nn_observerBlockMap[identifier] = observer;
}
#pragma mark - Setter & Getter
- (void)nn_setObserverBlockMap:(NSMutableDictionary *)nn_observerBlockMap {
objc_setAssociatedObject(self, @selector(nn_observerBlockMap), nn_observerBlockMap, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSMutableDictionary *)nn_observerBlockMap {
return objc_getAssociatedObject(self, @selector(nn_observerBlockMap));
}
@end
// 調用
self.kvoTestModel = [[KVOTestModel alloc] init];
NSString *identifier =
[self.kvoTestModel nn_addObserverForKeyPath:@"name"
options:NSKeyValueObservingOptionNew
usingBlock:^(id obj, NSString *keyPath, NSDictionary *change) {
NSLog(@"%@", change);
}];
self.kvoTestModel.name = @"Nero";
// 手動結束KVO監聽
[self.kvoTestModel nn_removeBlockObserverWithIdentifier:identifier];
// 自動結束KVO監聽
self.kvoTestModel = nil;
複製代碼