KVO在我們項目開發中,經常被用到,但很少會被人關注,但如果面試一些大公司,針對KVO的面試題可能如下:
- 知道KVO嘛,底層是怎么實現的?
- 如何動態的生成一個類?
今天我們圍繞上面幾個問題,我們先看KVO底層實現原理,以及怎么自己寫一個KVO?
一、KVO
1. KVO定義
KVO:可以監聽一個對象的某個屬性是否發生了改變,或者通知其他對象的指定屬性發生了改變。
2.KVO實現
2.1 監聽某個對象的屬性
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
2.2 實現協議
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary *)change context:(nullable void *)context;
2.3 移除監聽
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
下面是一個簡單的演示:
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. self.person = [[ZJPerson alloc] init]; [self.person setName:@"zhangsan"]; [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ [self.person setName:@"lisi"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ NSLog(@"%@", change); } - (void)dealloc{ [self.person removeObserver:self forKeyPath:@"name"]; }
運行結果

通過以上demo,我們來思考KVO為什么能監聽到屬性變化,底層又是怎么樣實現的呢?
3. KVO底層實現
KVO是通過isa-swizzling技術實現的。運行時根據原類創建一個中間類,這個中間類是原類的子類,並動態修改當前對象的isa指針指向中間類,並且將class 方法重寫,返回原類的class。蘋果建議通過class 實例方法來獲取對象類型。
在查看KVO底層實現,我們首先用runtime在添加監聽之前以及之后的類對象
1 NSLog(@"%@", object_getClass(self.person)); 2 [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; 3 NSLog(@"%@", object_getClass(self.person));
可以查看結果如下:
2018-05-19 22:48:18.726028+0800 KVO[33804:3059947] ZJPerson 2018-05-19 22:48:18.726535+0800 KVO[33804:3059947] NSKVONotifying_ZJPerson
通過上面發現,添加監聽之后,實例對象的類對象發生了改變,系統自動為我們動態添加了一個NSKVONotifying_+類名的類,改變屬性的值是通過setter方法進行實現,很明顯是系統已經動態生成了NSKVONotifying_ZJPerson類,並重寫了setter方法,新創建的NSKVONotifying_ZJPerson是ZJPerson的子類。所以不可以創建NSKVONotifying_ZJPerson類了,如果創建了NSKVONotifying_ZJPerson類,會報以下錯誤:
2018-05-19 22:56:32.223288+0800 KVO[33919:3068985] [general] KVO failed to allocate class pair for name NSKVONotifying_ZJPerson, automatic key-value observing will not work for this class
錯誤提示的是:創建NSKVONotifying_ZJPerson失敗。
那么問題又來了,重寫的setter方法內部又做了什么?我們再次利用runtime打印下面方法的實現。

通過上面發現,發現內部調用了Foundation框架的_NSSetObjectValueAndNotify方法,我們再次看看_NSSetObjectValueAndNotify內部的實現過程如下:
1. `-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]: 2. -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:usingBlock:]: 3. [ZJPerson setName:]; 4. `NSKeyValueDidChange: 5. `NSKeyValueNotifyObserver: 6. - (void)observeValueForKeyPath:ofObject:change:context
簡化成偽代碼如下:
- 調用willChangeValueForKey:
- 調用原來的setter實現
- 調用didChangeValueForkey
- didChangeValueForkey: 內部會調用observer的observeValueForKeyPath方法
1 - (void)setName:(NSString *)name{ 2 _NSSetObjectValueAndNotify(); 3 } 4 5 void _NSSetObjectValueAndNotify { 6 [self willChangeValueForKey:@"name"]; 7 [super setName:name]; 8 [self didChangeValueForKey:@"name"]; 9 } 10 11 - (void)didChangeValueForKey:(NSString *)key{ 12 [observe observeValueForKeyPath:key ofObject:self change:nil context:nil]; 13 }
拓展1
1. 子類會重寫父類的set、class、dealloc、_isKVOA方法
2. 當觀察對象移除所有的監聽后,會將觀察對象的isa指向原來的類
3. 當觀察對象的監聽全部移除后,動態生成的類不會注銷,而是留在下次觀察時候再使用,避免反復創建中間子類
拓展2》〉》NSKVONotifying_ZJPerson內部重寫了方法?
利用runtime打印方法列表
1 unsigned int count; 2 Method *methods = class_copyMethodList(object_getClass(self.person), &count); 3 4 for (NSInteger index = 0; index < count; index++) { 5 Method method = methods[index]; 6 7 NSString *methodStr = NSStringFromSelector(method_getName(method)); 8 9 NSLog(@"%@\n", methodStr); 10 }
打印結果
2018-05-20 08:57:07.883400+0800 KVO[35888:3218908] setName: 2018-05-20 08:57:07.883571+0800 KVO[35888:3218908] class 2018-05-20 08:57:07.883676+0800 KVO[35888:3218908] dealloc 2018-05-20 08:57:07.883793+0800 KVO[35888:3218908] _isKVOA
發現除了重寫了setName: 還重寫了class dealloc _isKVOA等方法
調用class方法可能里面實現是:
- (Class) class { return [ZXYPerson class] }
而不是NSKVONotifying_ZJPerson類,為了屏蔽了內部實現,隱藏了該類,如果想查看可以通過runtime的object_getClass()方法獲取真實運行時的情況
二、如何動態生成類
說到動態生成一個類,也就是利用了蘋果的runtime機制,下面我們來動態創建生成類。
2.1 創建類
Class customClass = objc_allocateClassPair([NSObject class], "ZJCustomClass", 0);
2.2 添加實例變量
// 添加實例變量 class_addIvar(customClass, "age", sizeof(int), 0, "i");
2.3 添加方法,V@:表示方法的參數和返回值
class_addMethod(customClass, @selector(hahahha), (IMP)hahahha, "V@:");
需要實現的方法:
void hahahha(id self, SEL _cmd) { NSLog(@"hahahha===="); } - (void)hahahha{ }
然后注冊到運行時環境
objc_registerClassPair(customClass);
下面是打印方法列表以及成員變量列表
1 #pragma mark - Util 2 3 - (NSString *)copyMethodsByClass:(Class)cls{ 4 unsigned int count; 5 Method *methods = class_copyMethodList(cls, &count); 6 7 NSString *methodStrs = @""; 8 9 for (NSInteger index = 0; index < count; index++) { 10 Method method = methods[index]; 11 12 NSString *methodStr = NSStringFromSelector(method_getName(method)); 13 14 methodStrs = [NSString stringWithFormat:@"%@ ", methodStr]; 15 } 16 17 free(methods); 18 19 return methodStrs; 20 } 21 22 - (NSString *)copyIvarsByClass:(Class)cls{ 23 unsigned int count; 24 Ivar *ivars = class_copyIvarList(cls, &count); 25 26 NSMutableString *ivarStrs = [NSMutableString string]; 27 28 for (NSInteger index = 0; index < count; index++) { 29 Ivar ivar = ivars[index]; 30 31 NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)]; //獲取成員變量的名字 32 33 NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; //獲取成員變量的數據類型 34 35 [ivarStrs appendString:@"\n"]; 36 [ivarStrs appendString:ivarName]; 37 [ivarStrs appendString:@"-"]; 38 [ivarStrs appendString:ivarType]; 39 40 } 41 42 free(ivars); 43 44 return ivarStrs; 45 }
如果想要了解更多的KVO,可以關注更新的博客
https://www.cnblogs.com/guohai-stronger/p/10272146.html
以上就是KVO的基本內容,希望通過本篇博客,大家對KVO原理以及基本使用有更深的了解!!!
