KVO探究

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

我們可以看出

  1. Class是個(gè)objc_class類型的結(jié)構(gòu)體
  2. 所有實(shí)例對(duì)象的本質(zhì)是objc_object類型的結(jié)構(gòu)體,里面存放這個(gè)實(shí)例對(duì)象的isa指針相味。
  3. id是一個(gè)objc_object類型的指針拾积,這應(yīng)該就是id可以指向任意對(duì)象的原因
  4. SEL是個(gè)objc_selector結(jié)構(gòu)體類型的指針,存放的是方法名
  5. 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í)有一張圖很明顯。

0_1326963670oeC1.gif.png

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)NSKeyValueObservingOptionNSKeyValueObservingOptionOld| 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值為newold只會(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屬性。
  • 重寫AnimalCat- (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)用willChangeValueForKeydidChangeValueForKey實(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ì)TextFieldplaceholderLabel實(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)用willChangeValueForKeydidChangeValueForKey在扰,無論是點(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ì)更好.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末猜旬,一起剝皮案震驚了整個(gè)濱河市脆栋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌洒擦,老刑警劉巖筹吐,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異秘遏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)嘉竟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門邦危,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舍扰,你說我怎么就攤上這事倦蚪。” “怎么了边苹?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵陵且,是天一觀的道長。 經(jīng)常有香客問我,道長慕购,這世上最難降的妖魔是什么聊疲? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮沪悲,結(jié)果婚禮上获洲,老公的妹妹穿的比我還像新娘。我一直安慰自己殿如,他們只是感情好猴娩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布款青。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栈幸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天歼指,我揣著相機(jī)與錄音卖丸,去河邊找鬼。 笑死胯努,一個(gè)胖子當(dāng)著我的面吹牛牢裳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叶沛,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蒲讯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了灰署?” 一聲冷哼從身側(cè)響起判帮,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎溉箕,沒想到半個(gè)月后晦墙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肴茄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年晌畅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寡痰。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抗楔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拦坠,到底是詐尸還是另有隱情连躏,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布贞滨,位于F島的核電站入热,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜勺良,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一绰播、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧郑气,春花似錦幅垮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至讳侨,卻和暖如春呵萨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背跨跨。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來泰國打工潮峦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人勇婴。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓忱嘹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親耕渴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拘悦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,392評(píng)論 8 265
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,718評(píng)論 0 9
  • KVC KVC定義 KVC(Key-value coding)鍵值編碼橱脸,就是指iOS的開發(fā)中础米,可以允許開發(fā)者通過K...
    暮年古稀ZC閱讀 2,144評(píng)論 2 9
  • OC語言基礎(chǔ) 1.類與對(duì)象 類方法 OC的類方法只有2種:靜態(tài)方法和實(shí)例方法兩種 在OC中,只要方法聲明在@int...
    奇異果好補(bǔ)閱讀 4,273評(píng)論 0 11
  • “你覺得t情商高嗎须眷?像是工作了很多年嗎乌叶?”f帶著蔑視的語氣問我。 最近t姐辭職結(jié)婚的消息柒爸,在辦公室里傳開了,f聽說...
    沐煊閱讀 564評(píng)論 0 1