這是一篇簡(jiǎn)單又豐富的簡(jiǎn)書活鹰,周末愉快!
一派哲、KVO概要及簡(jiǎn)單使用
KVO 就是一種監(jiān)聽臼氨,那是如何做到監(jiān)聽的呢?首先創(chuàng)建一個(gè)簡(jiǎn)單的 Class芭届,代碼如下:
#import <Foundation/Foundation.h>
@interface KVOObject : NSObject
// 姓名
@property (nonatomic, copy) NSString* name;
@end
#import "KVOObject.h"
@implementation KVOObject
@end
很簡(jiǎn)單储矩, 就一個(gè) Class感耙,然后定義了一個(gè) name 屬性而已。
一個(gè)簡(jiǎn)單的試驗(yàn)如下:
// 創(chuàng)建一個(gè) KVO 對(duì)象
KVOObject* kvObj = [[KVOObject alloc] init];
kvObj.name = @"HG";
// 添加 KVO 監(jiān)聽
[kvObj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
kvObj.name = @"CoderHG";
// 移除 KVO 監(jiān)聽, 在 iOS 10之前不移除的話直接 crash, 之后的就沒事了
[kvObj removeObserver:self forKeyPath:@"name"];
具體的監(jiān)聽方法代碼如下:
// KVO 的系統(tǒng)監(jiān)聽方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@, %@, %@", keyPath, object, change);
}
以上就是一個(gè)簡(jiǎn)單而完整的 KVO 的使用場(chǎng)景椰苟,但是具體是什么原理呢抑月?
二、KVO 的原理初步認(rèn)識(shí)
都知道一個(gè)對(duì)象一旦添加了 KVO 監(jiān)聽舆蝴,在本質(zhì)上是系統(tǒng)動(dòng)態(tài)的改變了該對(duì)象的 isa 指針谦絮。如果對(duì) isa 不了解的話,可以看這個(gè) OC 小專題洁仗。
想要知道 isa 有什么樣的變動(dòng)层皱,先實(shí)現(xiàn)如下一個(gè)方法:
// 打印具體的 cls 中的方法信息
- (NSString*)printMethodNamesOfClass:(Class)cls {
unsigned int count;
// 獲得方法數(shù)組
Method *methodList = class_copyMethodList(cls, &count);
// 存儲(chǔ)方法名
NSMutableString *methodNames = [NSMutableString string];
// 遍歷所有的方法
for (int i = 0; i < count; i++) {
// 獲得方法
Method method = methodList[i];
// 獲得方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
// 釋放
free(methodList);
// 返回類名與方法列表
return [NSString stringWithFormat:@"類名: %@ \n方法列表: %@", NSStringFromClass(cls), methodNames];
}
然后將以上的試驗(yàn)修改一下,如下:
// 常規(guī)用法
- (void)convention {
// 創(chuàng)建一個(gè) KVO 對(duì)象
KVOObject* kvObj = [[KVOObject alloc] init];
kvObj.name = @"HG";
Class cls = object_getClass(kvObj);
NSString* isaInfo = [self printMethodNamesOfClass:cls];
NSLog(@"\n\n添加 KVO 之前:\n%@", isaInfo);
// 添加 KVO 監(jiān)聽
[kvObj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
kvObj.name = @"CoderHG";
cls = object_getClass(kvObj);
isaInfo = [self printMethodNamesOfClass:cls];
NSLog(@"\n\n添加 KVO 之后:\n%@", isaInfo);
// 移除 KVO 監(jiān)聽, 在 iOS 10之前不移除的話直接 crash, 之后的就沒事了
[kvObj removeObserver:self forKeyPath:@"name"];
}
日志有如下的打釉省:
添加 KVO 之前:
類名: KVOObject
方法列表: .cxx_destruct, name, setName:
與
添加 KVO 之后:
類名: NSKVONotifying_KVOObject
方法列表: setName:, class, dealloc, _isKVOA
說明在添加 KVO 監(jiān)聽之后叫胖,isa 指針的值確實(shí)是變了,具體變化為:
- 1她奥、將之前的 KVOObject瓮增, 更換成 NSKVONotifying_KVOObject
- 2、在 NSKVONotifying_KVOObject 中重寫了 setName:哩俭、class 與 dealloc 方法绷跑,以及添加了一個(gè) _isKVOA 方法。
三凡资、KVO 與 KVC 的那一份藕斷絲連
關(guān)于 KVC砸捏,強(qiáng)烈建議看一下這篇文章KVC 的原理概述,接下來將會(huì)在這篇文章的基礎(chǔ)上做介紹隙赁。如果不看的話垦藏,可能你很難理解我所說的 非常規(guī) KVC 調(diào)用是什么意思。雖然伞访,我在這里僅僅是用到了那么一丁點(diǎn)的內(nèi)容掂骏。
首先創(chuàng)建一個(gè) Class, 代碼如下:
#import <Foundation/Foundation.h>
@interface KVO8KVCObject : NSObject
@end
#import "KVO8KVCObject.h"
@interface KVO8KVCObject ()
{
// 非常規(guī)試驗(yàn)
NSString* isGoddess;
}
@end
@implementation KVO8KVCObject
@end
那接下來厚掷,我們想要表達(dá)一個(gè)什么問題呢弟灼?
KVC 能否觸發(fā) KVO 監(jiān)聽?
看了上面的 KVO8KVCObject 定義蝗肪,我即將使用一個(gè) KVC 的非常規(guī)調(diào)用來介紹袜爪,具體代碼如下:
// KVO 與 KVC 那一段藕斷絲連的區(qū)域
- (void)kvo8kvc {
// 創(chuàng)建一個(gè) KVO8KVC 對(duì)象
KVO8KVCObject* kvO_CObj = [[KVO8KVCObject alloc] init];
// 通過 KVC 賦值
[kvO_CObj setValue:@"KJ" forKey:@"goddess"];
Class cls = object_getClass(kvO_CObj);
NSString* isaInfo = [self printMethodNamesOfClass:cls];
NSLog(@"\n\n添加 KVO 之前:\n%@", isaInfo);
// 添加 KVO 監(jiān)聽
[kvO_CObj addObserver:self forKeyPath:@"goddess" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
// 通過 KVC 賦值
[kvO_CObj setValue:@"JK" forKey:@"goddess"];
cls = object_getClass(kvO_CObj);
isaInfo = [self printMethodNamesOfClass:cls];
NSLog(@"\n\n添加 KVO 之后:\n%@", isaInfo);
// 移除 KVO 監(jiān)聽, 在 iOS 10之前不移除的話直接 crash, 之后的就沒事了
[kvO_CObj removeObserver:self forKeyPath:@"goddess"];
}
看一下具體的 Log 打印蠕趁,如下:
添加 KVO 之前:
類名: KVO8KVCObject
方法列表: .cxx_destruct
與
goddess, <KVO8KVCObject: 0x60c00001b080>, {
kind = 1;
new = JK;
old = KJ;
}
與
添加 KVO 之后:
類名: NSKVONotifying_KVO8KVCObject
方法列表: class, dealloc, _isKVOA
可以得出結(jié)論:
KVC 能觸發(fā) KVO 監(jiān)聽薛闪。
看到這里,也推翻了之前的一個(gè)結(jié)論:KVO 的正常觸發(fā)的入口是 setter 方法俺陋,其實(shí)不是這樣的豁延,就如同上面的這個(gè)實(shí)驗(yàn)昙篙,在 NSKVONotifying_KVO8KVCObject 與 KVO8KVCObject 中根本就沒有其對(duì)應(yīng)的 setter 方法。
四诱咏、面試題
KVO如何對(duì)集合類進(jìn)行監(jiān)聽?
這個(gè)面試題主要針對(duì)的是一個(gè)集合類中元素變動(dòng)的監(jiān)聽苔可,比如一個(gè)數(shù)組如何如何監(jiān)聽到 添加、插入與刪除袋狞。按照常規(guī)的 KVO 方式焚辅,是監(jiān)聽不到的,但是系統(tǒng)已經(jīng)為我們準(zhǔn)備了專門的 API苟鸯。
具體的介紹同蜻,看一參考 InterviewKVOController 中的具體實(shí)現(xiàn):
/** KVO如何對(duì)集合類進(jìn)行監(jiān)聽?
1. 需要借助一個(gè) Class (KVObject), 監(jiān)聽這個(gè) Class 實(shí)例對(duì)象中的集合屬性
2. 實(shí)際監(jiān)聽的是 mutableArrayValueForKey: 返回的集合類. 如同替身.
*/
本篇介紹,到這里就要告一段落了早处。在寫本簡(jiǎn)書的時(shí)候湾蔓,我有一個(gè)試驗(yàn) Demo # OC2Nature,可以作為一個(gè)參考砌梆。具體請(qǐng)看 KVO 目錄默责。
同時(shí)別忘了看看本專題的其它文章 OC 小專題
在之前也寫過關(guān)于 KVO 的簡(jiǎn)書,雖然那時(shí)候理解還不是太深入咸包,但是里面依舊是有新東西的桃序。感興趣的話可以去看看:
- 1、簡(jiǎn)單的KVO實(shí)現(xiàn)方式: 間接的實(shí)現(xiàn) KVO 的功能诉儒。
- 2葡缰、KVO與Category :A. @property 語法在不同場(chǎng)景的語義以及注意事項(xiàng)。 B. Category中實(shí)現(xiàn) KVO 監(jiān)聽忱反。