KVO使用及實現原理

KVO使用及實現原理

KVO使用

  • 對屬性進行監聽
  • 對屬性的屬性進行監聽
  • 容器監聽
  • 觸發(手動觸發,kvc賦值)

添加監聽函數

// 1.kvo對屬性的監聽
    [_person addObserver:self forKeyPath:NSStringFromSelector(@selector(name)) options:NSKeyValueObservingOptionNew context:nil];
    // 2.kvo屬性關聯,這個咱們是監聽dog的變化,那若是是dog屬性的變化,若是不作處理,是監聽不到的
    [_person addObserver:self forKeyPath:NSStringFromSelector(@selector(dog)) options:NSKeyValueObservingOptionNew context:nil];
    // 3.容器監聽
    [_person addObserver:self forKeyPath:NSStringFromSelector(@selector(mArr)) options:NSKeyValueObservingOptionNew context:nil];

觸發代碼指針

// 觸發基本知識點:KVO得經過set方法才能夠觸發
    
     NSString *name = NSStringFromSelector(@selector(name));
    
    // 手動觸發 這兩個方法必須是成對的,而後回調observeValueForKeyPath方法,至於爲何成對呢,猜測應該是蘋果作了處理,點進去官方的註釋也有說明這兩個方法必須成對存在
    // 若是想要手動觸發須要在被監聽類中實現+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key返回NO
    [_person willChangeValueForKey:name];
    [_person didChangeValueForKey:name];
    
    // kvc
    [_person setValue:@"123" forKey:name];
    
    // 容器
    NSMutableArray *arr1 = [_person mutableArrayValueForKey:NSStringFromSelector(@selector(mArr))];
    [arr1 addObject:@"123"];
    
    NSMutableArray *arr2 = [_person mutableArrayValueForKey:NSStringFromSelector(@selector(mArr))];
    [arr2 replaceObjectAtIndex:0 withObject:@"1234"];
    
    NSMutableArray *arr3 = [_person mutableArrayValueForKey:NSStringFromSelector(@selector(mArr))];
    [arr3 removeAllObjects];
    

    
    // 屬性關聯
    // 屬性關聯須要在被監聽類中實現+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key
    _person.dog.name = @"myDog";

被監聽類中實現的代碼code

// 這個方法不重寫,就是默認返回YES,若是重寫了返回NO,那麼就是須要手動觸發了,固然這個能夠根據參數key來判斷,區分監聽的key是手動仍是自動 
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    return YES;
}

// 這個方法用於監聽屬性的屬性的(屬性關聯)
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
    if ([key isEqualToString:@"dog"]) {
        return [[NSSet alloc] initWithObjects:@"_dog.name", nil];
    } else {
        return [super keyPathsForValuesAffectingValueForKey:key];
    }
}

移除監聽orm

懶得寫

KVO原理

1.在運行時的時候建立被監聽類的子類server

/** 1.動態生成一個類 */
    /** 1.1 動態生成一個類名 */
    NSString *oldClassName = NSStringFromClass(self.class);
    NSString *newClassName = [@"KVO_" stringByAppendingString:oldClassName];
    /** 定義一個類,繼承傳進來的類 */
    Class MyClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);

2.在子類中重寫父類屬性的set方法(因此KVO只能監聽屬性)對象

/** 添加set方法 */
    class_addMethod(MyClass, @selector(setName:), (IMP)setName, "V@:@");

3.註冊這個子類繼承

/** 註冊該類 */
    objc_registerClassPair(MyClass);

4.修改當前被監聽對象的isa指針指向子類遞歸

/** 修改isa指針 */
    object_setClass(self, MyClass);

5.實現set函數ci

/** set方法 */
void setName(id self,SEL _cmd,NSString * newName){
    /** 保存當前類型 */
    Class class = [self class];
    /** 調用父類方法 */
    object_setClass(self, class_getSuperclass(class));
    objc_msgSend(self, @selector(setName:),newName);
    /** 通知觀察者 */
    // 這裏有個屬性綁定,因此在添加觀察者的時候,就是將這個觀察者持有(就是使用屬性綁定來持有)
    id observer = objc_getAssociatedObject(self, @"objc");
    if (observer) {
        objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"new:":[self valueForKey:@"name"],@"kind:":@1},nil);
    }
    /** 改回子類 */
     object_setClass(self, class);
     
    // kvo就是在set方法中調用
    willChangeValueForKey:
    didChangeValueForKey:;
    那咱們這裏需不須要呢,固然不須要,由於咱們是本身實現了一套了,調這兩句也沒有用的
}

思考點

1.爲何kvc能夠觸發,kvc的原理rem

2.建立一個子類,若是建立的子類的類名,項目中恰好存在呢

若是項目中存在,那個建立的子類會nil,那麼這時候咱們可使用遞歸建立,爲nil就在類名後拼1或者其餘字符吧,直到成功爲止

3.objc_msgSend(self, @selector(setName:),newName);能夠傳值,那這個跟performSelector:有什麼關係,並且發現直接用objc_msgSend還比performSelector方便

4.爲何oc的每一個方法都有兩個隱式參數,這兩個是哪裏來的

oc調用方法是消息機制,表現形式就是 objc_msgSend(self, @selector(setName:),newName);那麼每一個方法在調用的本質都是使用 objc_msgSend那這個恰好就須要傳兩個參數,調用者和方法編號,也就是isa和sel
相關文章
相關標籤/搜索