1. 程式人生 > >通過子類實現KVO,淺析KVO底層原理

通過子類實現KVO,淺析KVO底層原理

通過手動實現KVO,對KVO底層原理有一定認識。

KVO只要是通過監聽set方法,從而實現對該物件的監聽。

要監聽set方法,有兩種實現方式,第一就是使用分類,重寫set方法,但是這樣就會覆蓋父類的set方法,所以不可行,pass掉。

第二就是使用子類,把父類的isa指標改為子類。然後呼叫父類色set方法,最後呼叫回撥方法,該方案可行。

首先是註冊監聽,在呼叫監聽方法的時候,會動態實現子類,把observer儲存到子類的屬性中(弱引用weak型別,不能使用strong,會造成迴圈引用),並且把型別為父類的self 的 isa指標更改為子類。在呼叫set方法的時候,首先需要呼叫父類的set方法(通過把isa指標改為父類,呼叫父類的set方法),然後再呼叫監聽回撥方法(把父類色isa指標改回子類,取出observer,通過observer呼叫監聽回撥方法)。

廢話不多說,直接上程式碼。

首先是結構目錄,其中NSObject+LLKVO是NSObject的子類,作用是動態實現觀察物件(比如Person)的子類。

 

NSObject+LLKVO的程式碼

#import <Foundation/Foundation.h>

@interface NSObject (LLKVO)
- (void)LL_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

@end

#import "NSObject+LLKVO.h"
#import <objc/message.h>

static NSString *OBSERVER = @"observer";
@implementation NSObject (LLKVO)
- (void)LL_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
    //1.建立一個類
    NSString *oldClassName = NSStringFromClass(self.class);
    NSString *newClassName = [@"LLKVO_" stringByAppendingString:oldClassName];
    Class myClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);
    //註冊類
    objc_registerClassPair(myClass);
    
    
    //2.重寫setName方法
    
    /**
     *class 給哪個類加方法
     *sel 方法編號
     *imp 方法實現(函式指標)
     *type 返回值型別
     */
    class_addMethod(myClass, @selector(setName:), (IMP)setName, "
[email protected]
:@"); //3.修改isa指標 object_setClass(self, myClass); //4.將資料觀察者儲存到當前物件 objc_setAssociatedObject(self, OBSERVER.UTF8String, observer, OBJC_ASSOCIATION_ASSIGN); } void setName(id self,SEL _cmd,NSString *newName) { //改為父類的型別,呼叫父類的set方法 Class newClass = [self class]; object_setClass(self, class_getSuperclass(newClass)); void (* action1)(id,SEL,NSString *) = (void (*) (id,SEL,NSString *))objc_msgSend; action1(self,@selector(setName:),newName); //改為子類 object_setClass(self, newClass); //取出觀察者 id observer = objc_getAssociatedObject(self, OBSERVER.UTF8String); if (observer) { void (* action)(id,SEL,NSString *,id,NSDictionary *,id) = (void (*) (id,SEL,NSString *,id,NSDictionary *,id)) objc_msgSend; action(observer,@selector(LL_observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"kind":@"1",@"new":newName},nil); } }

 

Person的程式碼,很簡單就定義了一個name屬性,重寫了下set方法

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end

#import "Person.h"

@implementation Person
- (void)setName:(NSString *)name {
    _name = name;
    NSLog(@"我重寫了set方法");
}
@end

 

 

ViewController中的程式碼

#import "ViewController.h"
#import "NSObject+LLKVO.h"
#import "Person.h"

@interface ViewController ()
@property (nonatomic, strong) Person *person;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.person = [Person new];
    [self.person LL_addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
}


- (void)LL_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  
    NSLog(@"%@",change);
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    static NSInteger a = 0;
    self.person.name = [NSString stringWithFormat:@"name -- %tu",++a];
}


@end

 

Log如下:

2018-12-04 11:43:57.742679+0800 KVO原理淺析[1494:324838] 我重寫了set方法

2018-12-04 11:43:57.743071+0800 KVO原理淺析[1494:324838] {

    kind = 1;

    new = "name -- 1";

}

 

通過自己實現KVO,明白了KVO的底層原理,蘋果底層肯定做的更加詳細,功能更加多,但是最基本的思想應該是一致的。

網上肯定有很多大神寫的比我詳細,底層原理剖析的更加徹底,僅以該部落格記錄自己對KVO的實現和理解,以後忘記了翻一下也可以快速想起。