1. 程式人生 > IOS開發 >iOS 探索KVO底層原理

iOS 探索KVO底層原理

KVO底層原理

上一篇文章中說到,KVO監聽成員變數無法收到回撥。先驗證一下是不是對的。

1.建立一個Person類,包含一個公有成員變數age,一個屬性變數name

@interface Person : NSObject{
    @public
    int age;
}
@property(strong,nonatomic)NSString * name;
複製程式碼

成員變數與屬性的區別: 屬性會自動生成settergetter方法,成員變數不會。

2.在ViewController中分別監聽兩個變數。

    self.p = [[Person alloc]init];
        
    [self.p addObserver:self for
KeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL]; [self.p addObserver:self forKeyPath:@"age" options:(NSKeyValueObservingOptionNew) context:NULL]; 複製程式碼

3.實現監聽回撥,log出改變內容以及被觀察者。

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@---%@"
,change,object); } 複製程式碼

4.分別給兩個變數賦值,用於觸發KVO

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.p.name = @"name";
    self.p->age = 10;
}
複製程式碼

.點語法與->的區別: .點語法呼叫了setter方法,而->是直接訪問成員變數

5.養成習慣,移除觀察者

-(void)dealloc{
    [self.p removeObserver:self forKeyPath:@"name"
]; [self.p removeObserver:self forKeyPath:@"age"]; } 複製程式碼

6.觸發KVO後的發現log內容只有一個

2019-03-07 21:53:13.105997+0800 KVO[84542:11106254] {
    kind = 1;
    new = name;
}---<Person: 0x6000007a6600>
複製程式碼

7.在@implementation中手動為agesetter方法

-(void)setAge:(int)newAge{
    age=newAge;
}
複製程式碼

8.修改touchesBegan方法中的age賦值方法

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.p.name = @"name";
    [self.p setAge:10];
}
複製程式碼

9.再次執行程式,觸發KVO,觀察log資訊,會發現列印了兩次。

2019-03-07 22:24:24.903100+0800 KVO[85477:11167107] {
    kind = 1;
    new = name;
}---<Person: 0x600002680a40>
2019-03-07 22:24:24.903477+0800 KVO[85477:11167107] {
    kind = 1;
    new = 10;
}---<Person: 0x600002680a40>
複製程式碼

由此可見,只有物件實現了setter方法,KVO對其進行的觀察,才會發起回撥。

物件新增觀察者後,發生了什麼

  • 新增觀察者後發生的變化

分別在新增觀察者前後列印一下物件名稱

self.p = [[Person alloc]init];
NSLog(@"%s",object_getClassName(self.p));
[self.p addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
NSLog(@"------分割線----------");
NSLog(@"%s",object_getClassName(self.p));
複製程式碼

列印結果為:

2019-03-08 10:13:13.099894+0800 KVO[88913:11408301] Person
2019-03-08 10:13:13.101816+0800 KVO[88913:11408301] ------分割線----------
2019-03-08 10:13:13.102021+0800 KVO[88913:11408301] NSKVONotifying_Person
複製程式碼

從列印結果我們可以觀察到,先前self.p指向的是Person,新增觀察者後變成了NSKVONotifying_Person。由此可知,在我們為一個物件新增觀察者之後,KVO會自動建立一個NSKVONotifying_<ClassName>

  • 新生成的類跟原來的類是什麼關係?

這裡有一個列印類跟子類的方法

-(void)printClasses:(Class)cls{
    //註冊類的總量
    int count = objc_getClassList(NULL,0);
    //建立一個數組,其中包含給定物件
    NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
    //獲取所有已經註冊的類
    Class *classes = (Class *)malloc(sizeof(Class)*count);
    objc_getClassList(classes,count);
    for (int i = 0; i<count; i++) {
        //classes[i]的父類 等於 cls
        if (cls == class_getSuperclass(classes[i])) {
            [mArray addObject:classes[i]];
        }
    }
    //釋放classes
    free(classes);
    NSLog(@"%@",mArray);
}
複製程式碼

在新增觀察者前,嘗試列印一下[Person class]

[self printClasses:[Person class]];
複製程式碼

列印結果為:

2019-03-07 22:52:02.545406+0800 KVO[86213:11217129](
    Person,Student
)
複製程式碼

然後新增一個觀察者,再重新列印一次

[self.p addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
[self printClasses:[Person class]];
複製程式碼

列印結果為:

2019-03-07 22:52:02.555585+0800 KVO[86213:11217129] (
    Person,"NSKVONotifying_Person",Student
)
複製程式碼

所以KVO會自動建立的NSKVONotifying_<ClassName>類繼承自<ClassName>

  • 新生成的類都做了什麼?

這裡有一個列印當前類執行的所有方法的方法

-(void)printClassAllMethod:(Class)cls{
    unsigned int  count = 0;
    //獲取當前類對應的方法列表
    Method *methods = class_copyMethodList(cls,&count);
    for (int i = 0 ; i<count; i++) {
        Method method = methods[i];
        //分別獲取SEL 跟 IMP
        SEL methodSEL = method_getName(method);
        IMP methodIMP = class_getMethodImplementation(cls,methodSEL);
        //列印SEL名稱以及IMP地址
        NSLog(@"%@---%p",NSStringFromSelector(methodSEL),methodIMP);
    }
    //釋放methods
    free(methods);
}
複製程式碼

觀察前後如果SEL數量發生改變,代表有對其進行了新添方法(為什麼沒刪除?子類沒辦法操作父類方法)。如果IMP地址發生了改變,代表對這個IMP進行的重寫。 驗證: 新建一個Student類繼承Person Person中實現方法-(void)say; Student重寫-(void)say;並且實現方法-(void)study; 順便重寫一下原有方法 class方法 分別列印-(void)printClassAllMethod:(Class)cls

[self printClassAllMethod:[Person class]];
NSLog(@"------分割線----------");
[self printClassAllMethod:[Student class]];
複製程式碼

列印結果:

2019-03-08 11:35:52.257273+0800 KVO[90824:11859981] say---0x109dfd260
2019-03-08 11:35:52.257500+0800 KVO[90824:11859981] ------分割線----------
2019-03-08 11:35:52.257659+0800 KVO[90824:11859981] say---0x109dfd1c0
2019-03-08 11:35:52.257774+0800 KVO[90824:11859981] study---0x109dfd1f0
2019-03-08 11:35:52.257976+0800 KVO[90824:11859981] class---0x109dfd220
複製程式碼

其中, say方法重寫,IMP地址發生改變,並且打印出了新增的study方法以及重寫的class方法。

分別列印[Person class][NSKVONotifying_Person class],對比原類跟新類都執行了什麼方法。

[self printClassAllMethod:[Person class]];
[self.p addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
NSLog(@"------分割線----------");
[self printClassAllMethod:NSClassFromString(@"NSKVONotifying_Person")];
複製程式碼

列印結果:

2019-03-08 10:47:01.419689+0800 KVO[89658:11656677] .cxx_destruct---0x10b19c1b0
2019-03-08 10:47:01.419936+0800 KVO[89658:11656677] name---0x10b19c150
2019-03-08 10:47:01.420134+0800 KVO[89658:11656677] setName:---0x10b19c170
2019-03-08 10:47:01.421109+0800 KVO[89658:11656677] ------分割線----------
2019-03-08 10:47:01.421352+0800 KVO[89658:11656677] setName:---0x10b4f663a
2019-03-08 10:47:01.421516+0800 KVO[89658:11656677] class---0x10b4f506e
2019-03-08 10:47:01.421649+0800 KVO[89658:11656677] dealloc---0x10b4f4e12
2019-03-08 10:47:01.421782+0800 KVO[89658:11656677] _isKVOA---0x10b4f4e0a
複製程式碼

所以,NSKVONotifying_Person重寫了setterclassdealloc 添加了_isKVOA

總結

  • 只有物件實現了setter方法,KVO對其進行的觀察,才會發起回撥。
  • 物件新增觀察者之後,KVO會自動建立一個NSKVONotifying_<ClassName>
  • 自動建立的NSKVONotifying_<ClassName>類繼承自<ClassName>
  • NSKVONotifying_<ClassName>重寫了setterclassdealloc,添加了_isKVOA