KVO原理解析


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

簡化成偽代碼如下:

  1. 調用willChangeValueForKey:
  2. 調用原來的setter實現
  3. 調用didChangeValueForkey
  4. 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原理以及基本使用有更深的了解!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM