深入理解KVC與KVO

KVC

介紹:http://www.lxweimin.com/p/45cbd324ea65

重點介紹查找過程:

當調用valueForKey:@”name“的代碼時,KVC對key的搜索方式不同于setValue:屬性值 forKey:@”name“,其搜索方式如下:

第一步:首先按get<Key>,<key>,is<Key>的順序方法查找getter方法,找到的話會直接調用。如果是BOOL或者Int等值類型, 會將其包裝成一個NSNumber對象。

第二步:如果上面的getter沒有找到,KVC則會查找countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes格式的方法。如果countOf<Key>方法和另外兩個方法中的一個被找到,那么就會返回一個可以響應NSArray所有方法的代理集合(它是NSKeyValueArray,是NSArray的子類),調用這個代理集合的方法,或者說給這個代理集合發送屬于NSArray的方法,就會以countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes這幾個方法組合的形式調用。還有一個可選的get<Key>:range:方法。所以你想重新定義KVC的一些功能,你可以添加這些方法,需要注意的是你的方法名要符合KVC的標準命名方法,包括方法簽名。
以key1為例,

-(NSUInteger)countOfKey1 {
    return 10;
}
- (id)objectInKey1AtIndex:(NSUInteger)index {
    return @(index);
}

第三步:如果上面的方法沒有找到,那么會同時查找countOf<Key>,enumeratorOf<Key>,memberOf<Key>格式的方法。如果這三個方法都找到,那么就返回一個可以響應NSSet所的方法的代理集合,和上面一樣,給這個代理集合發NSSet的消息,就會以countOf<Key>,enumeratorOf<Key>,memberOf<Key>組合的形式調用。

-(NSUInteger)countOfKey1 {
    return 10;
}
-(NSEnumerator *)enumeratorOfKey1 {
    NSSet * set = [NSSet setWithArray:@[@"set1",@"set2",@"set3"]];
    return [set objectEnumerator];
}
// 這個方法不會調用,但需要實現
- (NSSet *)memberOfKey1:(NSSet *)set {
    return set;
}

如果還沒有找到,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認行為),那么和先前的設值一樣,會按_<key>,_is<Key>,<key>,is<Key>的順序搜索成員變量名,這里不推薦這么做,因為這樣直接訪問實例變量破壞了封裝性,使代碼更脆弱。如果重寫了類方法+ (BOOL)accessInstanceVariablesDirectly返回NO的話,那么會直接調用valueForUndefinedKey:
還沒有找到的話,調用valueForUndefinedKey:

數組如何KVO

數組沒辦法直接監聽插入刪除修改,所以需要
聲明一個class,內部持有一個數組arrayM

@interface demoClass : UIViewController

@end

@interface demoClass ()

@property (nonautomic, strong) NSMutableArray *arrayM;

@end

@implementation demoClass
- (void)dealloc {
  [self removeObserver:self forKeyPath:@"arrayM"];
}
- (NSMutableArray *)arrayM {
    if (_arrayM == nil) {
        _arrayM = [@[] mutableCopy];
    }
    return _arrayM;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqual:@"arrayM"]) {
        NSLog(@"arrayM");
    }
}

- (void)viewDidLoad {
    [self addObserver:self forKeyPath:@"arrayM" options:NSKeyValueObservingOptionNew context:nil];

}

@end

觸發KVO

方法一:

...
- (void)viewDidLoad {
    [self addObserver:self forKeyPath:@"arrayM" options:NSKeyValueObservingOptionNew context:nil];
[self.arrayM insertObject:@"q" atIndex:0];
self.arrayM =  [self.arrayM mutableCopy] ;
}
....

但是observeValueForKeyPath這里接受的信息,不準確

方法二

使用KVC觸發KVO

...
- (void)viewDidLoad {
    [self addObserver:self forKeyPath:@"arrayM" options:NSKeyValueObservingOptionNew context:nil];
NSMutableArray *array_ =  [self mutableArrayValueForKey:@"arrayM"];
[array_ insertObject:@"1" atIndex:0];// 在insert方法里觸發KVO!!!!!!,不是set方法,觸發KVO的不一定是set方法
}
....

- (void)insertObject:(id)obj inArrayMAtIndex:(NSUInteger)index {
    [self.arrayM insertObject:obj atIndex:index];
}

- (void)removeObjectFromArrayMAtIndex:(NSIndexSet *)indexes {
    [self.arrayM removeObjectAtIndex:index];
}

- (void)setArrayM:(NSMutableArray *)arrayM {
  _arrayM = arrayM;
}

KVC搜索過程:
搜索insertObject:in<Key>AtIndex: , removeObjectFrom<Key>AtIndex: 或者 insert<Key>AdIndexes , remove<Key>AtIndexes 格式的方法
如果至少找到一個insert方法和一個remove方法,那么同樣返回一個可以響應NSMutableArray所有方法代理集合(類名是NSKeyValueFastMutableArray2,如果這個數組被KVO監聽返回NSKeyValueNotifyingMutableArray類型數組),那么給這個代理集合發送NSMutableArray的方法,以insertObject:in<Key>AtIndex: , removeObjectFrom<Key>AtIndex: 或者 insert<Key>AdIndexes , remove<Key>AtIndexes組合的形式調用。還有兩個可選實現的接口:replaceOnjectAtIndex:withObject:,replace<Key>AtIndexes:with<Key>:。

如果上步的方法沒有找到,則搜索set<Key>: 格式的方法,如果找到,那么發送給代理集合的NSMutableArray最終都會調用set<Key>:方法。 也就是說,mutableArrayValueForKey:取出的代理集合修改后,用set<Key>: 重新賦值回去去。這樣做效率會低很多。所以推薦實現上面的方法。

如果上一步的方法還還沒有找到,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認行為),會按_<key>,<key>,的順序搜索成員變量名,如果找到,那么發送的NSMutableArray消息方法直接交給這個成員變量處理。
如果還是找不到,則調用valueForUndefinedKey:。
關于mutableArrayValueForKey:的適用場景,我在網上找了很多,發現其一般是用在對NSMutableArray添加Observer上。如果對象屬性是個NSMutableArray、NSMutableSet、NSMutableDictionary等集合類型時,你給它添加KVO時,你會發現當你添加或者移除元素時并不能接收到變化。因為KVO的本質是系統監測到某個屬性的內存地址或常量改變時,會添加上- (void)willChangeValueForKey:(NSString *)key和- (void)didChangeValueForKey:(NSString *)key方法來發送通知,所以一種解決方法是手動調用者兩個方法,但是并不推薦,你永遠無法像系統一樣真正知道這個元素什么時候被改變。另一種便是利用使用mutableArrayValueForKey:了。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容