KVO原理
- KVO是基于
runtime
機(jī)制實(shí)現(xiàn)的
當(dāng)某個(gè)類的屬性對(duì)象第一次被觀察時(shí),系統(tǒng)就會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類亿柑,在這個(gè)派生類中重寫基類中任何被觀察屬性的setter
方法项阴。派生類在被重寫的setter
方法內(nèi)實(shí)現(xiàn)真正的通知機(jī)制 - 如果原類為
Person
吁断,那么生成的派生類名為NSKVONotifying_Person
- 每個(gè)類對(duì)象中都有一個(gè)
isa
指針指向當(dāng)前類炮障,當(dāng)一個(gè)類對(duì)象的第一次被觀察椿每,那么系統(tǒng)會(huì)偷偷將isa
指針指向動(dòng)態(tài)生成的派生類,從而在給被監(jiān)控屬性賦值時(shí)執(zhí)行的是派生類的setter
方法 - 鍵值觀察通知依賴于NSObject 的兩個(gè)方法:
willChangeValueForKey:
和didChangevlueForKey:
英遭;在一個(gè)被觀察屬性發(fā)生改變之前间护,willChangeValueForKey:
一定會(huì)被調(diào)用,這就 會(huì)記錄舊的值挖诸。而當(dāng)改變發(fā)生后汁尺,didChangeValueForKey:
會(huì)被調(diào)用,繼而 `observeValueForKey:ofObject:change:context: ``也會(huì)被調(diào)用多律。
弄清為什么改變isa指針的指向痴突,能修改調(diào)用的方法。
- 查看實(shí)例對(duì)象內(nèi)部結(jié)構(gòu)
打開xcode
狼荞,按command+shift+O
搜索objc.h
辽装,我們可以看到
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
我們可以看出
- Class是個(gè)
objc_class
類型的結(jié)構(gòu)體 - 所有實(shí)例對(duì)象的本質(zhì)是
objc_object
類型的結(jié)構(gòu)體,里面存放這個(gè)實(shí)例對(duì)象的isa指針相味。 - id是一個(gè)
objc_object
類型的指針拾积,這應(yīng)該就是id可以指向任意對(duì)象的原因 - SEL是個(gè)
objc_selector
結(jié)構(gòu)體類型的指針,存放的是方法名 - IMP函數(shù)指針丰涉,存放方法的具體實(shí)現(xiàn)地址拓巧。
- 再來看看
objc_class
struct objc_class {
Class _Nonnull isa // 所屬類的指針
#if !__OBJC2__
Class _Nullable super_class // 父類
const char * _Nonnull name // 類名
long version // 版本
long info // 運(yùn)行期使用的位標(biāo)識(shí)
long instance_size // 實(shí)例大小
struct objc_ivar_list * _Nullable ivars // 實(shí)例變量列表
struct objc_method_list * _Nullable * _Nullable methodLists // 方法列表
struct objc_cache * _Nonnull cache // 方法緩存
struct objc_protocol_list * _Nullable protocols // 協(xié)議列表
#endif
} OBJC2_UNAVAILABLE;
我們可以看出類中也有isa
指針,這個(gè)isa
指針是指向meta
元類中一死。實(shí)例對(duì)象肛度,類和元類的關(guān)系其實(shí)有一張圖很明顯。
isa
的指向是從實(shí)例對(duì)象->類對(duì)象->元類對(duì)象->根元類對(duì)象->自己投慈。
- 當(dāng)我們向一個(gè)實(shí)例對(duì)象發(fā)送消息承耿,即調(diào)用了實(shí)例方法時(shí)冠骄,在運(yùn)行時(shí)會(huì)順著
isa
指針指向的類對(duì)象中查找相對(duì)應(yīng)的方法,先從緩存中查找瘩绒,如果緩存未命中猴抹,則從方法列表中查找。如果未找到锁荔,則一直順著父類找到NSObject
蟀给,如果還未找到,則走消息轉(zhuǎn)發(fā)阳堕。 - 當(dāng)我們向一個(gè)類對(duì)象發(fā)送消息時(shí)跋理,即使用類方法調(diào)用,在運(yùn)行時(shí)會(huì)順著
isa
恬总,找到類的元類前普,在元類中查找類方法。先從緩存中查找壹堰,如果緩存未命中拭卿,則從方法列表中查找。如果未找到贱纠,則一直順著父元類一直找直到找到NSObject
峻厚,如果還未找到,則走消息轉(zhuǎn)發(fā)谆焊。
那我們可以知道惠桃,由isa指針指向的對(duì)象才是真正調(diào)用方法的對(duì)象。在類中存儲(chǔ)著實(shí)例對(duì)象的實(shí)例方法辖试,元類中存儲(chǔ)著類對(duì)象的類方法辜王,KVO中的isa- swizzle
就是交換isa的指向,本來是在A類中查找調(diào)用setter
方法罐孝,運(yùn)行時(shí)改成了在B類中查找調(diào)用setter
方法呐馆。
使用KVO的方法。
- 添加觀察者
_dog = [[Animal alloc] init];
[_dog setValue:@"牧塵" forKey:@"name"];
// 添加KVO
[_dog addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
// 觸發(fā)KVO
_dog.name = @"小明";
- KVO的監(jiān)聽
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"新值為%@",[change objectForKey:NSKeyValueChangeNewKey]);
NSLog(@"舊值為%@",[change objectForKey:NSKeyValueChangeOldKey]);
}
}
- 通知的移除
- (void)dealloc {
[_dog removeObserver:self forKeyPath:@"name"];
}
- 打印結(jié)果
2018-06-29 13:51:37.175922+0800 KVO與NSKeyValueObserving[38410:509677] 新值為小明
2018-06-29 13:51:37.176043+0800 KVO與NSKeyValueObserving[38410:509677] 舊值為牧塵
如果是簡單使用肾档,那知道這個(gè)就可以了摹恰。
使用探究
- 我們再看下
KVO
的方法,可以看出這是通過KVO
是對(duì)鍵值進(jìn)行的觀察怒见。
[_dog addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
- 那么我們知道KVC訪問的方式是通過下面兩中方式俗慈。
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
能用第一種setValue:forKey:
方式訪問的肯定也能通過setValue: forKeyPath:
方式訪問,但是通過keyPath
方式訪問變量不一定能用第一種方式訪問遣耍。
疑問一 :能通過KVO監(jiān)聽對(duì)象中另一個(gè)對(duì)象的屬性么闺阱?
新增Food
類,實(shí)例化為Animal
類的對(duì)象舵变。
#import <Foundation/Foundation.h>
@class Food;
/**
動(dòng)物類
*/
@interface Animal : NSObject
// 姓名
@property (nonatomic, strong) NSString *name;
// 食物
@property (nonatomic, strong) Food *food;
@end
#pragma mark --- 食物
// 食物類
@interface Food : NSObject
// 水果
@property (nonatomic, strong) NSString *fruit;
@end
- 對(duì)food.fruit進(jìn)行監(jiān)聽
[_dog setValue:@"蘋果" forKeyPath:@"food.fruit"];
[_dog addObserver:self forKeyPath:@"food.fruit" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
[_dog setValue:@"荔枝" forKeyPath:@"food.fruit"];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"food.fruit"]) {
NSLog(@"新值為%@",[change objectForKey:NSKeyValueChangeNewKey]);
NSLog(@"舊值為%@",[change objectForKey:NSKeyValueChangeOldKey]);
}
}
- 結(jié)果
2018-06-29 14:13:45.002673+0800 KVO與NSKeyValueObserving[38503:523330] 新值為荔枝
2018-06-29 14:13:45.002896+0800 KVO與NSKeyValueObserving[38503:523330] 舊值為蘋果
可以看出可以通過keyPath的方式對(duì)對(duì)象的對(duì)象進(jìn)行監(jiān)聽酣溃。
疑問二 : 通過keyPath
方式監(jiān)聽瘦穆,只局限于屬性監(jiān)聽么,實(shí)例對(duì)象不能監(jiān)聽么赊豌?
屬性可以通過點(diǎn)語法的方式訪問變量扛或,會(huì)調(diào)用
setter
方法。KVC
方式會(huì)查看如果有``setter方法碘饼,調(diào)用setter
方式賦值熙兔,如果沒有setter
方法,檢查accessInstanceVariablesDirectly
艾恼。如果accessInstanceVariablesDirectly
為YES住涉,順著_name
,_isName
,name
,isName
方式查找實(shí)例變量,有則賦值钠绍。沒有則拋異常舆声。測試通過實(shí)例變量,是不是
setter
方法能進(jìn)行KVO的監(jiān)聽
去除name的屬性柳爽,新增isName實(shí)例變量媳握。
@interface Animal() {
NSString *isName;
}
@end
@implementation Animal
@end
[_dog setValue:@"牧塵" forKey:@"name"];
[_dog addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
[_dog setValue:@"小飛" forKey:@"name"];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"新值為%@",[change objectForKey:NSKeyValueChangeNewKey]);
NSLog(@"舊值為%@",[change objectForKey:NSKeyValueChangeOldKey]);
}
}
- 結(jié)果
2018-06-29 14:36:51.210152+0800 KVO與NSKeyValueObserving[38565:537594] 新值為小飛
2018-06-29 14:36:51.210349+0800 KVO與NSKeyValueObserving[38565:537594] 舊值為牧塵
可以看出,KVO監(jiān)聽不僅能通過對(duì)屬性進(jìn)行監(jiān)聽磷脯,還能對(duì)類中的實(shí)例變量進(jìn)行監(jiān)聽毙芜。
- 為什么對(duì)實(shí)例變量也能監(jiān)聽在文章下說明。
手動(dòng)觸發(fā)KVO
- 在
NSKeyValueObserving
中有一個(gè)方法
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;
這個(gè)方法的含義是否自動(dòng)對(duì)key進(jìn)行觀察争拐,就是自動(dòng)觸發(fā)KVO
。默認(rèn)為YES晦雨。我們可以重寫方法設(shè)置為NO架曹,讓我們手動(dòng)觸發(fā)。
- 調(diào)用
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
方法來實(shí)現(xiàn)對(duì)key的手動(dòng)監(jiān)聽闹瞧。willChangeValueForKey:
顧名思義绑雄,將要改變的時(shí)候調(diào)用的方法,那么didChangeValueForKey:
就是已經(jīng)改變的時(shí)候調(diào)用的方法奥邮,當(dāng)NSKeyValueObservingOption
為NSKeyValueObservingOptionOld| NSKeyValueObservingOptionNew
時(shí)
- (void)viewDidLoad {
[super viewDidLoad];
_dog = [[Animal alloc] init];
[_dog setValue:@"牧塵" forKey:@"name"];
[_dog addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
NSLog(@"即將改變name");
[_dog willChangeValueForKey:@"name"];
// 改變值
[_dog setValue:@"小飛" forKey:@"name"];
// _dog.name = @"小明";
NSLog(@"已經(jīng)改變name");
[_dog didChangeValueForKey:@"name"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"調(diào)用了observeValueForKeyPath方法");
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"新值為%@",[change objectForKey:NSKeyValueChangeNewKey]);
NSLog(@"舊值為%@",[change objectForKey:NSKeyValueChangeOldKey]);
}
}
打印
2018-06-29 16:58:38.552276+0800 KVO與NSKeyValueObserving[43395:635717] 即將改變name
2018-06-29 16:58:38.552577+0800 KVO與NSKeyValueObserving[43395:635717] 已經(jīng)改變name
2018-06-29 16:58:38.552752+0800 KVO與NSKeyValueObserving[43395:635717] 調(diào)用了observeValueForKeyPath方法
2018-06-29 16:58:38.552873+0800 KVO與NSKeyValueObserving[43395:635717] 新值為小飛
2018-06-29 16:58:38.552998+0800 KVO與NSKeyValueObserving[43395:635717] 舊值為牧塵
- 可以看出
option
值為new
和old
只會(huì)在didChangeValueForKey :
已經(jīng)改變值后調(diào)用一次万牺。
當(dāng)option 為NSKeyValueObservingOptionPrior時(shí)
- 結(jié)果
2018-06-29 17:04:28.115616+0800 KVO與NSKeyValueObserving[43412:639884] 即將改變name
2018-06-29 17:04:28.115937+0800 KVO與NSKeyValueObserving[43412:639884] 調(diào)用了observeValueForKeyPath方法
2018-06-29 17:04:28.116069+0800 KVO與NSKeyValueObserving[43412:639884] 新值為(null)
2018-06-29 17:04:28.116329+0800 KVO與NSKeyValueObserving[43412:639884] 舊值為牧塵
2018-06-29 17:04:28.116471+0800 KVO與NSKeyValueObserving[43412:639884] 已經(jīng)改變name
2018-06-29 17:04:28.116589+0800 KVO與NSKeyValueObserving[43412:639884] 調(diào)用了observeValueForKeyPath方法
2018-06-29 17:04:28.116711+0800 KVO與NSKeyValueObserving[43412:639884] 新值為小飛
2018-06-29 17:04:28.116816+0800 KVO與NSKeyValueObserving[43412:639884] 舊值為牧塵
- 我們可以得出結(jié)果
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
方法會(huì)在willChangeValueForKey :
調(diào)用時(shí)觸發(fā)一次,在didChangeValueForKey :
調(diào)用時(shí)觸發(fā)一次洽腺,其中調(diào)用willChangeValueForKey
后是否觸發(fā)方法和設(shè)置的NSKeyValueObservingOptions
有關(guān)脚粟。
runtime到底是什么時(shí)候修改了對(duì)象的isa指針。
- 重寫model類的description方法
- (NSString *)description {
// 查看當(dāng)前isa指針指向的類
Class runtimeClass = object_getClass(self);
// 當(dāng)前類的父類
Class superClass = class_getSuperclass(runtimeClass);
NSLog(@"runtimeClass = %@, superClass = %@",runtimeClass, superClass);
return @"";
}
- 在添加觀察者前后打印代碼
[_dog description];
[_dog addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
[_dog description];
- 結(jié)果
2018-06-29 18:09:05.005066+0800 KVO與NSKeyValueObserving[43833:680180] runtimeClass = Animal, superClass = NSObject
2018-06-29 18:09:05.005591+0800 KVO與NSKeyValueObserving[43833:680180] runtimeClass = NSKVONotifying_Animal, superClass = Animal
我們可以看出蘸朋,KVO在- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
方法調(diào)用后核无,在運(yùn)行時(shí)動(dòng)態(tài)生成了一個(gè)原來類的子類NSKVONotifying_XXX
,導(dǎo)致我們通過點(diǎn)語法或者 KVC
為屬性賦值的時(shí)候,調(diào)用的是這個(gè)isa指向的類NSKVONotifying_XXX里的方法列表中的setter方法藕坯。
回到上面一個(gè)問題团南,實(shí)例變量為什么也能監(jiān)聽噪沙?
對(duì)實(shí)例變量通過KVC訪問賦值,由于isa指針的混淆吐根,KVC實(shí)際是對(duì)NSKVONotifying_XXX
類調(diào)用正歼。但是由于實(shí)例變量無法繼承到NSKVONotifying_XXX
中,但是KVO仍然會(huì)對(duì)NSKVONotifying_XXX
調(diào)用- (void)setValue:(id)value forKey:(NSString *)key
方法拷橘,NSKVONotifying_XXX
調(diào)用失敗局义,會(huì)調(diào)用super類的- (void)setValue:(id)value forKey:(NSString *)key
,即賦值成功。我們可以測試一下
- 創(chuàng)建一個(gè)
Animal
類的子類Cat
膜楷。Animal
類中有個(gè)實(shí)例變量name旭咽。不存在name屬性。 - 重寫
Animal
和Cat
的- (void)setValue:(id)value forKey:(NSString *)key
方法赌厅。
- (void)setValue:(id)value forKey:(NSString *)key {
[super setValue:value forKey:key];
NSLog(@"animal類中的value = %@", value);
}
- 我們先將
Cat
類中的super
鏈打斷
- (void)setValue:(id)value forKey:(NSString *)key {
NSLog(@"cat類中的value = %@",value);
// [super setValue:value forKey:key];
}
- 測試打印
Cat *cat = [[Cat alloc] init];
[cat setValue:@"小妖精" forKey:@"name"];
NSLog(@"catName = %@",[cat valueForKey:@"name"]);
- 結(jié)果
cat
賦值失敗穷绵,因?yàn)?code>cat中沒有name
實(shí)例變量
NSKeyValueObserving[44918:845002] cat類中的value = 小妖精
2018-07-02 10:10:49.221572+0800 KVO與NSKeyValueObserving[44918:845002] catName = (null)
- 我們將
cat
中的super
鏈連接.
- (void)setValue:(id)value forKey:(NSString *)key {
NSLog(@"cat類中的value = %@",value);
[super setValue:value forKey:key];
}
打印結(jié)果,賦值成功,調(diào)用了父類中的- (void)setValue:(id)value forKey:(NSString *)key
進(jìn)行賦值特愿,父類中有name實(shí)例變量仲墨。
NSKeyValueObserving[44937:846921] cat類中的value = 小妖精
2018-07-02 10:13:04.639062+0800 KVO與NSKeyValueObserving[44937:846921] animal類中的value = 小妖精
2018-07-02 10:13:04.639263+0800 KVO與NSKeyValueObserving[44937:846921] catName = 小妖精
KVO
分別在KVC賦值的前后調(diào)用willChangeValueForKey
和didChangeValueForKey
實(shí)現(xiàn)監(jiān)聽調(diào)用- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
告訴我們值已經(jīng)改變了。
這樣KVC調(diào)用的賦值揍障,在我們實(shí)際開發(fā)中也有用到目养,類似TextField
有@"_placeholderLabel.font"
這樣的鍵值,我們在子類化TextField
毒嫡,創(chuàng)建subClassTextField
這樣一個(gè)子類后癌蚁,仍然可以用KVC的方法通過鍵值對(duì)TextField
的placeholderLabel
實(shí)例變量進(jìn)行賦值。
查看KVC對(duì)實(shí)例變量調(diào)用KVO監(jiān)聽過程中兜畸,didChangeValueForKey
的調(diào)用時(shí)機(jī)努释。
- 設(shè)置系統(tǒng)自動(dòng)觸發(fā)KVO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
return YES;
}
重寫- (void)setValue:(id)value forKey:(NSString *)key
方法
- (void)setValue:(id)value forKey:(NSString *)key {
// 查看當(dāng)前isa指針指向的類
Class runtimeClass = object_getClass(self);
// 當(dāng)前類的父類
Class superClass = class_getSuperclass(runtimeClass);
NSLog(@"runtimeClass = %@, superClass = %@",runtimeClass, superClass);
NSLog(@"賦值前,name = %@",isName);
[super setValue:value forKey:key];
NSLog(@"賦值后咬摇,name = %@",isName);
}
- 輸出結(jié)果
2018-07-02 11:31:28.751652+0800 KVO與NSKeyValueObserving[45238:898392] runtimeClass = NSKVONotifying_Animal, superClass = Animal
2018-07-02 11:31:28.752193+0800 KVO與NSKeyValueObserving[45238:898392] 賦值前伐蒂,name = 牧塵
2018-07-02 11:31:28.752513+0800 KVO與NSKeyValueObserving[45238:898392] 調(diào)用了observeValueForKeyPath方法
2018-07-02 11:31:28.752667+0800 KVO與NSKeyValueObserving[45238:898392] 新值為小飛
2018-07-02 11:31:28.752795+0800 KVO與NSKeyValueObserving[45238:898392] 舊值為牧塵
2018-07-02 11:31:28.752940+0800 KVO與NSKeyValueObserving[45238:898392] 賦值后,name = 小飛
我們可以看出肛鹏,KVC調(diào)用實(shí)例屬性觸發(fā)didChangeValueForKey
是在- (void)setValue:(id)value forKey:(NSString *)key
方法中逸邦。
我們可以看出KVO監(jiān)聽的過程實(shí)際上是對(duì)值的監(jiān)聽,在值改變的前后調(diào)用willChangeValueForKey
和didChangeValueForKey
在扰,無論是點(diǎn)語法還是KVC都是直接或間接對(duì)值的改變缕减。
手動(dòng)實(shí)現(xiàn)一個(gè)KVO
寫法思路都一樣,推薦一個(gè)文章
// 總結(jié)如下
1 通過NSSelectorFromString 獲取setter的sel健田。 通過classGetInstanceMethod 獲取 method烛卧。 判斷method是否存在, 不存在return。
2 判斷當(dāng)前類是否是kvo類总放。 通過object_getClass 獲取isa指向的類呈宇, 通過NSStringFromClass 回去類名。 判斷類名是否有你的前綴局雄。
3 如果不是kvo的類甥啄,則要去創(chuàng)建kvo的類。 通過NSClassFromString 獲取類炬搭, 判斷kvo名稱的類是否已經(jīng)被注冊蜈漓。 如果注冊,則返回注冊的類宫盔。 如果沒注冊融虽,通過objc_allocateClassPair創(chuàng)建類, 通過class_addMethod 添加 class方法的實(shí)現(xiàn)灼芭,修改kvo class方法的實(shí)現(xiàn), 學(xué)習(xí)Apple的做法, 隱瞞這個(gè)kvo_class有额。 然后objc_registerClassPair 注冊類
4 通過class_addMethod 為kvo class添加setter方法的實(shí)現(xiàn), 實(shí)現(xiàn)監(jiān)聽。 通過 valueForKey 獲取 原始的指彼绷, 然后調(diào)用原類的setter方法賦值巍佑。 從關(guān)聯(lián)對(duì)象中取出觀察數(shù)組, callback告訴外部修改.
5 把對(duì)應(yīng)的kvo信息存進(jìn) 關(guān)聯(lián)對(duì)象數(shù)組里面寄悯。
哪些情況下使用kvo會(huì)崩潰萤衰,怎么防護(hù)崩潰
使用不當(dāng) 會(huì)crash,比如:
- 添加和移出不是成對(duì)出現(xiàn)且存在多線程添加KVO的情況,經(jīng)常遇到的crash是移出 - 內(nèi)存dealloc的時(shí)候 或者對(duì)象銷毀前沒有正確移出Observer
如何防護(hù)?
- 1.注意移出對(duì)象 匹配
- 2.內(nèi)存野指針問題,一定要在對(duì)象銷毀前移出觀察者
- 3.可以使用第三方庫BlockKit添加KVO,blockkit內(nèi)部會(huì)自動(dòng)移除Observer避免crash.
kvo的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 方便兩個(gè)對(duì)象間同步狀態(tài)(keypath)更加方便,一般都是在A類要觀察B類的屬性的變化.
- 非侵入式的得到某內(nèi)部對(duì)象的狀態(tài)改變并作出響應(yīng).(就是在不改變原來對(duì)象類的代碼情況下即可做出對(duì)該對(duì)象的狀態(tài)變化進(jìn)行監(jiān)聽)
- 可以嵌入更改前后的兩個(gè)時(shí)機(jī)的狀態(tài). - 可以通過Keypaths對(duì)嵌套對(duì)象的監(jiān)聽.
缺點(diǎn):
- 需要手動(dòng)移除觀察者,不移除容易造成crash.
- 注冊和移出成對(duì)匹配出現(xiàn).
- keypath參數(shù)的類型String, 如果對(duì)象的成員變量被重構(gòu)而變化字符串不會(huì)被編譯器識(shí)別而報(bào)錯(cuò).
- 實(shí)現(xiàn)觀察的方式是復(fù)寫NSObjec的相關(guān)KVO的方法,應(yīng)該更加面向protocol的方式會(huì)更好.