天天看點

KVO分析KVO

KVO

概述

KVO的全稱是NSKeyValueObserving,對象采用的一種非正式協定,當其他對象的指定屬性發生變化時,通知對象。由于KVO的機制,隻對對象的屬性起作用,一般繼承自NSObject都支援KVO。

您可以觀察任何對象屬性,包括簡單屬性、一對一關系和多對多關系。

使用

KVO使用通常需要三個步驟

  1. 通過

    addObserver:forKeyPath:options:context:

    注冊觀察者,來觀察keyPath的變化
  2. 當相對于被觀察對象的指定鍵路徑的值發生變化時,通知觀察對象的

    observeValueForKeyPath:ofObject:change:context:

    這個方法,觀察者應當實作該方法
  3. 當不再需要觀察對象屬性變化時,通過調用

    removeObserver:forKeyPath:

    或者

    removeObserver:forKeyPath:context:

    阻止觀察者對象接收與接收此消息的對象相關的鍵路徑指定的屬性的更改通知.

注:

addObserver

removeObserver

應當一一對應

注冊方法

在注冊時,

options

的值為

NSKeyValueObservingOptions

枚舉類型

。當為

NSKeyValueObservingOptionNew

NSKeyValueObservingOptionOld

時,表示接收新值和舊值,預設為隻接收新值。如果想在注冊觀察者後,立即接收一次回調,則可以加入

NSKeyValueObservingOptionInitial

枚舉

實作原理

KVO

是通過

isa-swizzling

技術實作的。在運作時根據原類建立一個中間類,這個中間類是原類的子類,并動态修改目前對象的

isa

指向中間類。并且将

class

方法重寫,傳回原類的

Class

。是以蘋果建議在開發中不應該依賴

isa

指針,而是通過

class

執行個體方法來擷取對象類型。

驗證

` _stu = [Student new];
  _stu.stu_id = index;
  NSLog(@"beforeClass:%@",  object_getClass(_stu));
 [_stu addObserver:self forKeyPath:@"age"    options:NSKeyValueObservingOptionNew context:nil];
 NSLog(@"afterClass:%@", object_getClass(_stu));`           

可以檢視輸出結果為:

`2018-12-30 14:34:01.761066+0800 KVO[1485:239369]           beforeClass:Student`           

2018-12-30 14:34:01.761533+0800 KVO[1485:239369] afterClass:NSKVONotifying_Student

自定義KVO

寫一個Student的分類

Student+KVO.h

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

Student+KVO.m

#import "NSObject+KVO.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation Student  (KVO)
-(void)XK_addObserver:(NSObject *)observer  forKeyPath:(NSString *)keyPath options: (NSKeyValueObservingOptions)options context:(void *)context{
NSString* oldClassName = NSStringFromClass([self class]);
NSString* newClassName = [@"XKKVONotyfing_" stringByAppendingString:oldClassName];
const char * newName = [newClassName UTF8String];
Class newClass = objc_allocateClassPair([self class], newName, 0);
class_addMethod(newClass,@selector(setAge:), (IMP)setAge, "v@:i");
objc_registerClassPair(newClass);
object_setClass(self, newClass);
objc_setAssociatedObject(self,          
(__bridge const void *)@"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}

void setAge(id self, SEL _cmd, int age) {
Class myClass = [self class];
object_setClass(self, class_getSuperclass([self class]));
//調用父類
id (*msgSend) (id, SEL,...) = (void *)objc_msgSend;
msgSend(self, @selector(setAge:),age);
id observer = objc_getAssociatedObject(self, (__bridge const void *)@"objc");
 id (*msgSendObj) (id, SEL,id,...) = (void *)objc_msgSend;
   msgSendObj(observer,@selector(XK_observeValueForKeyPath:ofObject:change:context:),@"age",@"age",nil,nil);
//改為子類
object_setClass(self, myClass);
}           

觀察者中調用

<[_stu XK_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];      
- (void)XK_observeValueForKeyPath:(NSString )keyPath ofObject:(id)object change:(NSDictionary)change context:(void *)context{      
NSLog(@"keyPath:%@",keyPath);
 NSLog(@"ofObject:%@",object);
 NSLog(@"change:%@",change);
 NSLog(@"afterClass:%@", object_getClass(_stu));
}</code></pre>