iOS KVO底層詳解、內部原理

kvo:key-value-observing:鍵值監聽,可以監聽某個對象屬性的值

#import "Person.h"

@interface ViewController()
@property (nonatomic, strong) Person *person;
@property (nonatomic, strong) Person *person2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // Do any additional setup after loading the view.
    
    self.person = [[Person alloc] init];
    self.person2 = [[Person alloc] init];

    
    
    NSKeyValueObservingOptions optips = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    
    [self.person addObserver:self forKeyPath:@"age" options:optips context:@"sdf"];
    
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.person.age = @"12";
    self.person2.age = @"12";

}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"監聽到%@的%@屬性發生了變化-%@ -%@",object, keyPath, change, context);
}

 

結果是:
2018-07-12 22:34:17.544 newxc[2119:242191] 監聽到<Person: 0x60000001d0c0>的age屬性發生了變化-{
    kind = 1;
    new = 12;
    old = "<null>";
} -sdf

 

只會發生一次監聽,那爲什麼同樣一個person類,set方法都在Person類裏,那麼爲什麼只有被監聽的能發生監聽呢?

 

原因如下,請看證實性的:

可以看到打印出來的isa指針都不一樣,所以說,這裏內部是被修改了,那具體的內部實現是什麼樣的呢,下面模擬一下這個內部實現,

#import "NSKVONotifying_Person.h"

// 僞代碼、僞代碼、僞代碼、僞代碼、僞代碼

@implementation NSKVONotifying_Person

- (void)setAge:(NSString *)age
{
    _NSSetIntValueAndNotify();
}

void _NSSetIntValueAndNotify()
{
    [self willChangeValueForKey:@"age"];
    [super setAge:age];
    [self didChangeValueForKey@"age"];
}

- (void)didChangeValueForKey:(NSString *)key
{
    // 運行時,調用此方法,子類沒有 就調用父類的此方法。
    // 通知父類的調用父類的kvo,進行實現
    [self performSelector:@selector(observeValueForKeyPath:ofObject:change:context:)];
}
@end

performSelector

這個NSKVONotifying_Person,是自己模擬剛纔打印出來的那個類,集成自Person類,內部有set方法,繼而下面的實現,還會調用父類的[super setAget:age]方法,讓這個age值真的改變,然後再會把這個監聽發出去,達到監聽的目的,具體的邏輯看下面

沒有加kvo監聽的邏輯實現如下:

所以可以得知,加了kvo和沒有加kvo走的並不是一套,加了kvo的走的是子類NSKVONotifying_Person的set方法,並達到監聽的目的。

 

添加監聽後不是一個類,驗證一:

驗證二:(地址)

驗證三

上面這個證明didChangeValueForKey 裏面是調用了監聽方法的。

首先:

證明這個類中確實是這幾個方法,

- (void)printMethodNameOfClass:(Class)clas
{
    unsigned int cout;
    // 獲得方法數組
    Method *methodlist = class_copyMethodList(clas, &cout);
    
    // 儲存方法名
    NSMutableString *muString = [NSMutableString string];
    
    // 遍歷所有的方法
    for (int i = 0; i < cout; i ++) {
        // 獲得方法
        Method methd = methodlist[i];
        
        // 獲得方法名
        NSString *methodStr = NSStringFromSelector(method_getName(methd));
        
        // 拼接方法名
        [muString appendString:methodStr];
        [muString appendString:@", "];
        
    }
    // 釋放
    free(methodlist);
    
    // 打印一下類和方法名
    NSLog(@"%@ %@",clas, muString);
    
}
調用
    [self printMethodNameOfClass:[self.person class]];
    [self printMethodNameOfClass:object_getClass(self.person)];
打印結果:
2018-07-17 23:18:24.911 newxc[4213:1394789] Person setAge:, age, .cxx_destruct, 
2018-07-17 23:18:24.911 newxc[4213:1394789] NSKVONotifying_Person setAge:, class, dealloc, _isKVOA,

能不能監聽的 前提 是有沒有實現set方法。

 

面試題:

1:ios用什麼方式實現對一個對象的kvo?(kvo的本質是什麼?)

利用runtimeAPI動態生成一個子類,並讓instance對象的isa指向這個全新的類,當修改instance對象的屬性時,會調用Foundation的——NSSetXXXValueAndNotify函數:(willChangeValueForKey; 父類原來的setter方法;didChangeValueForKey(內部會觸發監聽器(observer)的監聽方法(observerValueForKeyPath:)),)

 

2:如何手動觸發kvo?

(不調用setage方法)

手動調用willChangeValueForKey 和didChangeValueForKey方法,即可直接觸發kvo。

 

3:直接修改成員變量會觸發KVO麼?

不會觸發KVO,(添加kvo的person實例,其實是NSKVONotyfing_person類,再調用setter方法,不是調用person的setter方法,而是NSKVONotyfing_person的setter方法,因爲修改成員變量不是setter方法賦值self.person->age=@"12", 所以就無所謂調用NSKVONotyfing_person類的setter方法,也就不會實現kvo。)

 

結尾:這裏說一下心得:

我們平時處理代碼,初級的情況下,都會以編譯期查詢方法和寫方法的方式來思考問題,但其實,oc是一個動態語言,所以很多黑魔法和方法做出來都可以在真正的調用時刻-runtime時刻進行處理,這也就是真正的調用,只有類和對象的本質,方法的本質,沒有所謂的.h 和.m的限制,只是我們習慣了oc給我們的和剛開始接觸的.h 和.m規範,並沒有瞭解到真正的內部原理是什麼。就像web是沒有編譯的,所以它們執行代碼的真正調用和流程,就跟我們runtime調用是一樣的,所以,不用把runtime想象的多麼複雜,是真正的實現過程而已,蘋果給我們一個途徑去了解它,修改它,  就是runtime。所以需要知道本質的方法、本質的類是什麼、運行時調用函數和方法這些 都需要我們進行探索,而不僅僅限制於表面的.h的編譯期的限制。這僅僅是我自己的想法,如果有錯誤,歡迎指認。

另外補充一下,

問題:如果在項目中對person進行了監聽,也創建了一個NSKVONotifying_Person類,那麼會編譯通過麼?

答案是可以的。

編譯不通過是因爲,在編譯時刻,兩個類名相同,所以報錯,解決方法,就是不讓兩個同名的都在編譯時刻進入,因爲kvo是運行時刻創建的,並不在編譯時刻,在編譯時刻只有一個NSKVONotifying_Person,所以不報錯,可以通過。而程序的初始化,是先編譯後運行。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章