博客连接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;
复制代码