1. 程式人生 > >KVO 使用及原理

KVO 使用及原理

mar pan int 屬性 gist tcl string類 nta objects

KVO的基本原理大概是這樣的

當一個對象被觀察時, 系統會新建一個子類NSNotifying_A ,在子類中重寫了對象被觀察屬性的 set方法, 並且改變了該對象的 isa 指針的指向(指向了新建的子類) , 當屬性的值發生改變了, 會調用子類的set方法, 然後發出通知

一. KVO 的基本使用

給_person對象 添加觀察者self, 當person對象的name的值發生改變的時候, 會觸發observer方法

_person = [Person new]; p.name = @"oldName"; 
//添加觀察者
// [p addObserver:self forKeyPath:@"dog" options:NSKeyValueObservingOptionNew context:nil];

[_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];

// 所觀察的對象的keyPath 發生改變的時候, 會觸發
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(
@"%@",keyPath); NSLog(@"%@",change); }

二. 當keyPath 為對象時, 改對象有許多屬性, 怎麽辦?

在person類中,重寫這個方法, 設置需要觀察的屬性 , 註意:"_dog.level"

//返回一個容器, 裏面放的是NSString類型
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
    
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    
//觀察dog對象中的所有屬性 if ([key isEqualToString:@"dog"]) { keyPaths = [keyPaths setByAddingObjectsFromArray:@[@"_dog.level",@"_dog.age"]]; } return keyPaths; }

三. 手動觸發KVO

系統默認該對象的所有屬性 都能被觀察到 ,重寫下面方法, 可以單獨設置某個屬性不能被觀察

//默認 yes, 默認自動觀察所有屬性
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
    return YES;
}
//返回NO, 則不能被默認觀察到name
+ (BOOL)automaticallyNotifiesObserversOfName{ return NO; } + (BOOL)automaticallyNotifiesObserversOfAge{ return YES; }

當 name 發生改變的時, 手動觸發, 發送通知

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //手動發通知
    //即將改變(發一次通知)
    [_person willChangeValueForKey:@"name"];
     _person.name = @"dddd";
     //已經改變(發一次通知),一共發了兩次通知
    [_person didChangeValueForKey:@"name"];
}

四. 自定義KVO

根據kvo的原理, 可以自定義一個kvo, 建一個NSObject的分類, 添加方法

@interface NSObject (XSKVO)

- (void)xs_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

@end

通過runtime的方式, 動態創建一個類, 並給該類添加方法

#import "NSObject+XSKVO.h"
 #import <objc/runtime.h>

@implementation NSObject (XSKVO)

- (void)xs_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
    
    //1.新建一個類
    NSString *className = [@"XSKVO" stringByAppendingString: NSStringFromClass([self class])];
    Class newClass = objc_allocateClassPair([self class], className.UTF8String, 0);
     //註冊類
    objc_registerClassPair(newClass);
    //2.修改 調用者類型
    object_setClass(self, newClass);
    
    //3.給子類添加set方法(子類裏面沒有set方法的)
    //OC方法:方法編號SEL ,方法實現IMP
    class_addMethod(newClass, @selector(setName:), xssetName, "");
   
}

/*
 隱藏的參數:
 self  方法的調用者
 _cmd  方法的編號
 
 */
void xssetName(id self,SEL _cmd, NSString *newName){
    NSLog(@"自定義的實現%@",newName);
    //方案一:通過消息機制 發送消息 -observeValueForKeyPath
    
    
}

@end

五. 其他

關於容器類(如:NSMutableArray)的觀察, 當通過addObject: 向數組中添加對象, 不會觸發KVO, 因為並沒有觸發set方法,

解決方法: 通過KVC 方法 - mutableArrayValueForKey:

KVO 使用及原理