天天看點

KVO底層實作--寫一個自己的KVO

KVO底層原理  

Person * p =[[Person alloc] init];

[p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];

p這個對象一旦添加觀察者後,系統會将這個Person這個類的isa指針修改為 NSKVONotifying_Person,NSKVONotifying_Person 這個類是Person類的子類, 這樣每次通路p其實通路NSKVONotifying_Person這樣類名的p,然後系統會重寫p age屬性的set方法.在set方法裡面調用

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context這個方法.完了再調用super 方法.

KVO底層實作--寫一個自己的KVO

知道原理,我們就可以自己來寫一個自己的KVO驗證一下

模仿系統給NSObejct添加一個分類,具有添加自己的觀察者的能力

//

//  NSObject+MY_KVO.h

//  arc

//

//  Created by SGQ on 16/6/2.

//  Copyright © 2016年 GQ. All rights reserved.

//

#import <Foundation/Foundation.h>

@interface NSObject (MY_KVO)

- (void)My_addObserver:(NSObject *_Nonnull)observer forKeyPath:(NSString *_Nonnull)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

@end

實作

//

//  NSObject+MY_KVO.m

//  arc

//

//  Created by SGQ on 16/6/2.

//  Copyright © 2016年 GQ. All rights reserved.

//

#import "NSObject+MY_KVO.h"

#import "MY_KVONotifying_Person.h"

#import <objc/runtime.h>

@implementation NSObject (MY_KVO)

-(void)My_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{

    // 修改isa指針(runtime) 系統的MY_KVONotifying_Person這個類是動态生成的,我們直接手動建立

    object_setClass(self, [MY_KVONotifying_Person class]);

   // 給對象動态添加屬性,之前文章介紹過了.目的是儲存observer,好在set方法裡面拿到,調用

My_addObserver:forKeyPath:options:context:這個方法

    objc_setAssociatedObject(self, (__bridge const void *)(keyPath), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

@end

二,重寫MY_KVONotifying_Person set方法

//

//  MY_KVONotifying_Person.m

//  arc

//

//  Created by SGQ on 16/6/2.

//  Copyright © 2016年 GQ. All rights reserved.

//

#import "MY_KVONotifying_Person.h"

#import "NSObject+MY_KVO.h"

#import <objc/runtime.h>

@implementation MY_KVONotifying_Person

- (void)setAge:(int)age{

   id observer = objc_getAssociatedObject(self, @"age");

    if (observer && [observer respondsToSelector:@selector(My_addObserver:forKeyPath:options:context:)]) {

        [observer My_addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];

    }

    [super setAge:age];

}

@end

三.使用一下

#import "ViewController.h"

#import "Person.h"

#import "NSObject+MY_KVO.h"

@interface ViewController ()

@property (nonatomic,strong)Person * p;

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    Person * p =[[Person alloc] init];

    [p My_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];

    _p= p;

}

- (void)My_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{

    NSLog(@"age++ 自己的KVO");

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

     _p.age ++ ;

}

我們可以看到列印

KVO底層實作--寫一個自己的KVO

繼續閱讀