初探
Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects.
KVO
, 即Key-value observing
,也就是我們常說的鍵值觀察跌榔,它是是一種機(jī)制眨攘,允許將其他對(duì)象的指定屬性的更改通知給指定對(duì)象畅形。
KVO
機(jī)制對(duì)應(yīng)用程序中模型層和控制器層之間的通信特別有用齿风,控制器對(duì)象通常觀察模型對(duì)象的屬性榕吼,而視圖對(duì)象通過控制器觀察模型對(duì)象的屬性继低,模型對(duì)象可能會(huì)觀察其他模型對(duì)象(通常是確定從屬值何時(shí)更改)竹宋,甚至是自身(再次確定從屬值何時(shí)更改)劳澄。
比如說,我們的視圖控制器viewController
上有一個(gè)label
用來顯示模型對(duì)象person
的一個(gè)屬性name
逝撬,我們需要的實(shí)時(shí)顯示浴骂,當(dāng)name
發(fā)生變化的時(shí)候,label
的顯示也會(huì)及時(shí)變化宪潮,那么我們就可以給person
添加一個(gè)觀察者viewController
溯警,用以觀察name
屬性,當(dāng)name
發(fā)生變化的時(shí)候狡相,viewController
就可以及時(shí)刷新label
梯轻,這樣就做到了模型層和控制器層的通信。
KVO
的使用
使用步驟
- 使用
addObserver:forKeyPath:options:context:
方法將觀察者注冊(cè)到觀察對(duì)象 -
observeValueForKeyPath:ofObject:change:context:
在觀察者內(nèi)部實(shí)現(xiàn)以接受更改通知消息 - 當(dāng)觀察者
removeObserver:forKeyPath:
不再應(yīng)該接收消息時(shí)尽棕,使用該方法注銷觀察者喳挑。該方法至少在從內(nèi)存釋放觀察者之前調(diào)用。
下面我們配合一個(gè)例子進(jìn)行探究:
@interface TPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSMutableArray *hobbies;
@property (nonatomic, copy) NSString *downloadProgress;
@property (nonatomic, assign) double writtenData;
@property (nonatomic, assign) double totalData;
@end
@implementation TPerson
@end
然后在ViewController
里實(shí)現(xiàn)如下代碼:
@interface ViewController ()
@property (nonatomic, strong) TPerson *person;
@property (nonatomic, assign) int pNumber;
@end
1. 注冊(cè)鍵值觀察
被觀察對(duì)象首先通過調(diào)用下面方法向觀察者注冊(cè)自己滔悉,并且傳入要觀察的屬性的keyPath
伊诵,另外還指定了一個(gè)options
參數(shù)和一個(gè)上下文指針context
來管理通知的各個(gè)方面。
- (void)addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context;
該方法的參數(shù)如下:
-
(NSObject *)observer
: 觀察者 -
(NSString *)keyPath
: 要觀察的屬性 -
(NSKeyValueObservingOptions)options
: 設(shè)置回調(diào)的內(nèi)容和時(shí)機(jī) -
(nullable void *)context
: 上下文指針用以區(qū)分被觀察者和觀察屬性
我們?cè)賮砜纯?code>options的取值有什么含義:
-
NSKeyValueObservingOptionNew = 0x01
: 變化之后的新值 -
NSKeyValueObservingOptionOld = 0x02
: 變化之前的舊值 -
NSKeyValueObservingOptionInitial = 0x04
: 變化之前通知 -
NSKeyValueObservingOptionPrior = 0x08
: 變化之后通知
注意回官,options
可以填寫多個(gè)曹宴。
我們可以使用context
指針用來區(qū)分每一個(gè)監(jiān)聽,這樣在通知回調(diào)的時(shí)候可以直接根據(jù)context
進(jìn)行區(qū)分處理歉提,這樣更簡(jiǎn)潔一些笛坦。使用方式如下:
static void *PersonNickNameContext = &PersonNickNameContext;
當(dāng)不使用context
指針的時(shí)候区转,需要傳入NULL
,而不能傳入nil
版扩,因?yàn)椴皇?code>id類型废离。
2. 接收變化回調(diào)
觀察者必須實(shí)現(xiàn)下面方法,才能接收到觀察對(duì)象的變化礁芦。
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
context:(nullable void *)context;
-
(nullable NSString *)keyPath
: 觀察的屬性 -
(nullable id)object
: 觀察對(duì)象 -
(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
: 返回的觀察信息 -
(nullable void *)context
: 上下文指針
-
NSKeyValueChangeKindKey
: 返回的更改信息的時(shí)機(jī)蜻韭,1是變化之前,2是變化之后 -
NSKeyValueChangeNewKey
: 返回的值是新值 -
NSKeyValueChangeOldKey
: 返回值是舊值 -
NSKeyValueChangeIndexesKey
: 觀察的屬性是數(shù)組或者NSSet
的下標(biāo)變化 -
NSKeyValueChangeNotificationIsPriorKey
: 觀察通知的時(shí)機(jī)是否是Prior
-
NSKeyValueChangeSetting = 1
: 數(shù)據(jù)的設(shè)置 -
NSKeyValueChangeInsertion = 2
: 集合的插入 -
NSKeyValueChangeRemoval = 3
: 集合的移除 -
NSKeyValueChangeReplacement = 4
: 集合的替換
當(dāng)被觀察對(duì)象是集合對(duì)象宴偿,在NSKeyValueChangeKindKey
字段中會(huì)包含NSKeyValueChangeInsertion
湘捎、NSKeyValueChangeRemoval
、NSKeyValueChangeReplacement
的信息窄刘,表示集合對(duì)象的操作方式。
3. 移除觀察
當(dāng)我們不需要觀察者繼續(xù)觀察對(duì)象了舷胜,就需要移除觀察者娩践。在觀察者被釋放之前必須移除觀察者,否則會(huì)出現(xiàn)BUG
烹骨。移除之前必須注冊(cè)翻伺,否則就會(huì)崩潰。
一般沮焕,我們?cè)谟^察者初始化期間(例如在init
中或viewDidLoad
中)注冊(cè)為觀察者吨岭,在釋放過程中(通常在中dealloc
)注銷,確保在觀察者從內(nèi)存中釋放之前將其注銷峦树。
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
接著上面的例子辣辫,我們?cè)?code>viewDidLoad進(jìn)行注冊(cè)KVO
,回調(diào)通知的策略給name
屬性使用改變之前的魁巩,hobbies
使用改變之后的急灭。:
static void *PersonNameContext = &PersonNameContext;
static void *PersonHobbyContext = &PersonHobbyContext;
- (void)viewDidLoad {
self.person = [[TPerson alloc] init];
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial context:PersonNameContext];
self.person.hobbies = [NSMutableArray array];
[self.person addObserver:self forKeyPath:@"hobbies" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:PersonHobbyContext];
[self.person addObserver:self forKeyPath:@"downloadProgress" options: NSKeyValueObservingOptionNew context:NULL];
}
監(jiān)聽回調(diào):
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == PersonNameContext) {
NSLog(@"===name===change==%@=====", change);
} else if (context == PersonHobbyContext) {
NSLog(@"===hobbies===change==%@=====", change);
} else {
NSLog(@"===downloadProgress===change==%@=====", change);
}
}
然后在下面方法中改變觀察的屬性的值:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.pNumber++;
self.person.name = [NSString stringWithFormat:@"%@-%d-%@", @"Number", self.pNumber, @"Person"];
switch (self.pNumber % 4) {
case 1:
case 3:
{
[[self.person mutableArrayValueForKeyPath:@"hobbies"] addObject:@"add"];
}
break;
case 2:
{
[[self.person mutableArrayValueForKeyPath:@"hobbies"] removeObject:@"add"];
}
break;
case 0:
{
[[self.person mutableArrayValueForKeyPath:@"hobbies"] replaceObjectAtIndex:0 withObject:@"replace"];
}
break;
default:
break;
}
NSLog(@"===%@===", self.person.hobbies);
self.person.writtenData += 10;
self.person.totalData += 20;
}
然后在dealloc
方法中移除觀察:
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"name"];
[self.person removeObserver:self forKeyPath:@"hobbies"];
[self.person removeObserver:self forKeyPath:@"downloadProgress"];
}
運(yùn)行程序,控制臺(tái)輸出:
===name===change=={
kind = 1;
new = "<null>";
}=====
因?yàn)?code>name屬性的監(jiān)聽策略使用了NSKeyValueObservingOptionInitial
谷遂,所以進(jìn)入頁(yè)面就會(huì)響應(yīng)葬馋。點(diǎn)擊一下頁(yè)面:
===name===change=={
kind = 1; // NSKeyValueChangeSetting
new = "Number-1-Person"; // NSKeyValueObservingOptionNew
old = "<null>"; // NSKeyValueObservingOptionOld
}=====
// NSKeyValueObservingOptionPrior
===hobbies===change=={
indexes = "<_NSCachedIndexSet: 0x600002949ac0>[number of indexes: 1 (in 1 ranges), indexes: (0)]"; // NSKeyValueChangeIndexesKey
kind = 2; // NSKeyValueChangeInsertion
notificationIsPrior = 1; // NSKeyValueObservingOptionPrior
}=====
===hobbies===change=={
indexes = "<_NSCachedIndexSet: 0x600002949ac0>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
kind = 2;
new = (
add
);
}=====
繼續(xù)點(diǎn)擊頁(yè)面,控制臺(tái)會(huì)輸出hobbies
的removeObject
和replaceObjectAtIndex
的監(jiān)聽肾扰,kind
也會(huì)有相應(yīng)的變化畴嘶,此處就不一一演示了。
注意集晚,觀察的屬性是數(shù)組的時(shí)候不能直接是add
等方法:
[self.person.hobbies addObject:@"addd"]
由于KVO
是基于KVC
窗悯,而直接使用add
等方法是不會(huì)觸發(fā)KVC
的,所以我們要使用
[[self.person mutableArrayValueForKeyPath:@"hobbies"] addObject:@"add"]
符合KVO
的使用標(biāo)準(zhǔn)
當(dāng)我們要使用KVO
觀察某個(gè)對(duì)象的時(shí)候甩恼,必須得確保該對(duì)象符合KVO
的使用標(biāo)準(zhǔn)才可以蟀瞧。
- 該類和屬性必須要符合
KVC
沉颂,因?yàn)?code>KVO的實(shí)現(xiàn)依托于KVC
。KVO
支持的數(shù)據(jù)類型與KVC
相同悦污,包括Objective-C
對(duì)象以及基本數(shù)據(jù)類型和結(jié)構(gòu)體铸屉。 - 該類能為該屬性發(fā)出
KVO
更改的通知。 - 當(dāng)有依賴關(guān)系的時(shí)候切端,注冊(cè)合適的依賴
key
彻坛。
發(fā)出KVO
通知
發(fā)出KVO
通知有兩種方式,一種是自動(dòng)通知踏枣,另外一種是手動(dòng)通知昌屉。
自動(dòng)通知
自動(dòng)通知需要該類和屬性必須要符合KVC
,使用KVC
的方式對(duì)屬性進(jìn)行賦值茵瀑。如下面方法:
- (void)setValue:(nullable id)value forKey:(NSString *)key
手動(dòng)通知
在某些情況下间驮,我們希望控制通知過程。例如马昨,最大程度地減少出于應(yīng)用程序特定原因而不必要的觸發(fā)通知竞帽,或?qū)⒍鄠€(gè)更改分組為一個(gè)通知。這是就可以使用手動(dòng)更改通知鸿捧。
手動(dòng)和自動(dòng)通知不是互斥的屹篓。當(dāng)我們使用手動(dòng)通知的時(shí)候,第一步需要重寫automaticallyNotifiesObserversForKey:
匙奴,并且返回NO
堆巧;第二步在在更改值之前調(diào)用willChangeValueForKey:
、在更改值之后調(diào)用didChangeValueForKey:
泼菌。當(dāng)一個(gè)類里既存在需要手動(dòng)通知的屬性谍肤,又存在需要自動(dòng)通知的屬性,在這個(gè)方法里就需要進(jìn)行判斷灶轰,分別處理谣沸。
- (void)setName:(NSString *)name {
if (_name != name) {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
return NO;
}
如果操作的屬性是集合類型,需要使用以下方法:
- (void)willChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key;
- (void)didChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key;
注冊(cè)合適的依賴key
在許多情況下笋颤,一個(gè)屬性的值取決于另一個(gè)或多個(gè)其他屬性的值乳附。如果一個(gè)屬性的值發(fā)生更改,則派生屬性的值也應(yīng)標(biāo)記為更改伴澄。我們需要確保當(dāng)一個(gè)附屬的屬性發(fā)生變化時(shí)赋除,其父屬性也能收到改變通知。
一對(duì)一關(guān)系
要自動(dòng)觸發(fā)一對(duì)一關(guān)系的通知非凌,需要重寫 keyPathsForValuesAffectingValueForKey:
或?qū)崿F(xiàn)遵循其定義的用于注冊(cè)從屬key
的式的方法举农。
- 使用
keyPathsForValuesAffectingValueForKey
方法
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"downloadProgress"]) {
NSArray *affectingKeys = @[@"totalData", @"writtenData"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
- (NSString *)downloadProgress {
if (self.writtenData == 0) {
self.writtenData = 10;
}
if (self.totalData == 0) {
self.totalData = 100;
}
return [[NSString alloc] initWithFormat:@"%f",1.0f*self.writtenData/self.totalData];
}
運(yùn)行程序,控制臺(tái)輸出:
===downloadProgress===change=={
kind = 1;
new = "0.100000";
}=====
===downloadProgress===change=={
kind = 1;
new = "0.083333";
}=====
- 使用
keyPathsForValuesAffectingDownloadProgress
代替keyPathsForValuesAffectingValueForKey
方法也可以達(dá)到同樣的效果敞嗡。
+ (NSSet<NSString *> *)keyPathsForValuesAffectingDownloadProgress {
return [NSSet setWithObjects:@"totalData", @"writtenData", nil];
}
一對(duì)多關(guān)系
假設(shè)有一個(gè)Department
對(duì)象颁糟,Department
又包含多個(gè)employees
航背,所以該對(duì)象與Employee
形成了一對(duì)多關(guān)系,而Employee
具有薪金屬性棱貌。當(dāng)Department
對(duì)象具有一個(gè)totalSalary
屬性玖媚,該屬性取決于所有雇員的薪水,可以在observeValueForKeyPath
進(jìn)行處理婚脱。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == totalSalaryContext) {
[self updateTotalSalary];
}
}
- (void)updateTotalSalary {
[self setTotalSalary:[self valueForKeyPath: @"employees.@ sum.salary"]];
}
- (void)setTotalSalary:(NSNumber *)newTotalSalary {
if (totalSalary今魔!= newTotalSalary) {
[self willChangeValueForKey:@“totalSalary”];
_totalSalary = newTotalSalary;
[self didChangeValueForKey:@“totalSalary”];
}
}
-(NSNumber *)totalSalary {
return _totalSalary;
}
KVO
原理
看完KVO
的使用,我們來看一下KVO
的實(shí)現(xiàn)原理障贸,官方文檔這么說:
Automatic key-value observing is implemented using a technique called isa-swizzling.
The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.
You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.
KVO
是使用isa-swizzling
技術(shù)實(shí)現(xiàn)的错森。這個(gè)isa
指針指向?qū)ο蟮念悾S護(hù)一個(gè)派發(fā)表篮洁。該派發(fā)表實(shí)質(zhì)上包含該類實(shí)現(xiàn)的方法的指針以及其他數(shù)據(jù)涩维。
在為對(duì)象的屬性注冊(cè)觀察者時(shí),將修改觀察對(duì)象的isa
指針嘀粱,讓其指向一個(gè)中間類而不是原來的類激挪。表現(xiàn)出來的現(xiàn)象就是,isa
指針的值不一定指向正確類锋叨,因?yàn)樗赡苤赶蛞粋€(gè)中間類。所以不要依靠isa
指針來確定類宛篇,相反娃磺,應(yīng)該使用類方法確定實(shí)例對(duì)象的類。
我們可以使用上述demo
來驗(yàn)證一下叫倍,在注冊(cè)觀察者的前后設(shè)置log
偷卧,運(yùn)行程序:
從控制臺(tái)我們可以看出,此時(shí)生成了一個(gè)中間類NSKVONotifying_TPerson
吆倦。
當(dāng)觀察一個(gè)對(duì)象A
時(shí)听诸,KVO
機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象A
當(dāng)前類的中間類,其類名為NSKVONotifying_A
蚕泽,該類繼承自對(duì)象A
的本類晌梨,并為這個(gè)新的類重寫了被觀察屬性keyPath
的setter
方法。setter
方法會(huì)負(fù)責(zé)在調(diào)用原setter
方法之前和之后须妻,通知所有觀察對(duì)象屬性值的更改情況仔蝌。
被觀察對(duì)象A
的isa
指針從指向原來的A
類,被KVO
機(jī)制修改為指向NSKVONotifying_A
類荒吏,來實(shí)現(xiàn)當(dāng)前類屬性值改變的監(jiān)聽敛惊。
需要注意的是,此處生成的是一個(gè)類绰更,而不是對(duì)象瞧挤,修改的是原對(duì)象的isa
指向锡宋,讓其指向中間類
下面我們來驗(yàn)證一下NSKVONotifying_A
和A
類的關(guān)系到底是不是繼承?我們?cè)谧?cè)前后使用下面打印一下TPerson
類及其子類的名稱:
- (void)printClasses:(Class)cls {
// 注冊(cè)類的總數(shù)
int count = objc_getClassList(NULL, 0);
// 創(chuàng)建一個(gè)數(shù)組特恬, 其中包含給定對(duì)象
NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
// 獲取所有已注冊(cè)的類
Class* classes = (Class*)malloc(sizeof(Class)*count);
objc_getClassList(classes, count);
for (int i = 0; i < count; i++) {
if (cls == class_getSuperclass(classes[i])) {
[mArray addObject:classes[i]];
}
}
free(classes);
NSLog(@"==classes==%@", mArray);
}
控制臺(tái)輸出:
可以看出执俩,生成的中間類是被觀察對(duì)象的類的子類
接著我們給TPerson
添加一個(gè)實(shí)例變量,看看屬性和實(shí)例變量由于setter
方法的差異會(huì)不會(huì)有不同的結(jié)果鸵鸥。
@public
NSString *ivarName;
在touchesBegan
事件奠滑,給ivarName
賦值:
self.person->ivarName = @"ivarName";
運(yùn)行程序,控制臺(tái)輸出:
==change=={
kind = 1;
new = "Number-1-Person";
old = "<null>";
}==
可以發(fā)現(xiàn)妒穴,并沒有ivarName
的改變通知宋税,這也說明了ivarName
沒有setter
方法,所以并不能發(fā)出改變通知讼油。
同樣杰赛,我們可以在注冊(cè)前后打印類的方法來分析前后的差異:
- (void)printClassAllMethod:(Class)cls{
unsigned int count = 0;
Method *methodList = class_copyMethodList(cls, &count);
for (int i = 0; i < count; i++) {
Method method = methodList[i];
SEL sel = method_getName(method);
IMP imp = class_getMethodImplementation(cls, sel);
NSLog(@"--sel--%@--imp--%p",NSStringFromSelector(sel),imp);
}
free(methodList);
}
- (void)viewDidLoad {
[self printClassAllMethod:[TPerson class]];
NSLog(@"--注冊(cè)之前--");
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial context:PersonNameContext];
NSLog(@"--注冊(cè)之后--");
[self printClassAllMethod:NSClassFromString(@"NSKVONotifying_TPerson")];
}
由于注冊(cè)之后就生成了中間類NSKVONotifying_TPerson
,所以注冊(cè)后我們直接打印中間類的方法矮台》ν停控制臺(tái)輸出結(jié)果如下:
--sel--setHobbies:--imp--0x10f10ef70
--sel--hobbies--imp--0x10f10ef50
--sel--writtenData--imp--0x10f10eff0
--sel--setWrittenData:--imp--0x10f10f010
--sel--totalData--imp--0x10f10f040
--sel--setTotalData:--imp--0x10f10f060
--sel--.cxx_destruct--imp--0x10f10f090
--sel--name--imp--0x10f10ef20
--sel--setName:--imp--0x10f10eb90
--sel--downloadProgress--imp--0x10f10ee10
--sel--setDownloadProgress:--imp--0x10f10efb0
--注冊(cè)之前--
--注冊(cè)之后--
--sel--setName:--imp--0x7fff25721c7a
--sel--class--imp--0x7fff2572073d
--sel--dealloc--imp--0x7fff257204a2
--sel--_isKVOA--imp--0x7fff2572049a
從以上結(jié)果可以看出,在注冊(cè)之前瘦赫,屬性name
和實(shí)例變量ivarName
的區(qū)別就是沒有setter/getter
辰晕。另外,在注冊(cè)之后确虱,又打印出來了setName
方法含友,NSKVONotifying_TPerson
作為TPerson
的子類,如果還有setName
方法的話校辩,說明NSKVONotifying_TPerson
窘问,重寫了父類的setName
的方法,也就是中間類重寫了原來類的setter宜咒。
既然中間類NSKVONotifying_TPerson
重寫了dealloc
方法惠赫,那么它肯定是在該方法里做了某些處理,我們?cè)谟^察者的dealloc
方法里故黑,移除觀察者的之后儿咱,加入以下方法:
NSLog(@"--移除之后--%s--", object_getClassName(self.person));
運(yùn)行程序,控制臺(tái)輸出:
--移除之后--TPerson--
可以看出倍阐,移除觀察者之后概疆,被觀察對(duì)象的isa
指針會(huì)指回到原來的A
類。
總結(jié)
KVO
是基于KVC
的一種機(jī)制峰搪,通過觀察回調(diào)岔冀,可用于不同層面的通信。其使用步驟如下:
- 注冊(cè)觀察者
- 接受更改回調(diào)
- 移除觀察者
KVO
原理
- 動(dòng)態(tài)生成了一個(gè)中間類,該類是被觀察對(duì)象的類的子類使套,類名是
NSKVONotifying_A
罐呼,被觀察對(duì)象A
的isa
指針從指向原來的A
類,被KVO
機(jī)制修改為指向NSKVONotifying_A
類 - 觀察的是中間類
NSKVONotifying_A
重寫的setter
方法 - 中間類
NSKVONotifying_A
重寫了很多方法:-
setter
: 發(fā)送改變通知 -
class
: 返回被觀察對(duì)象的類 -
dealloc
: 釋放相關(guān)方法 -
_isKVOA
: 標(biāo)志是生成的中間類
-
- 移除觀察的之后, 被觀察對(duì)象的
isa
指針會(huì)指回到原來的A
類 - 移除觀察之后中間類
NSKVONotifying_A
不會(huì)銷毀
參考文獻(xiàn): Aplle官方文檔