KVO底層實(shí)現(xiàn)原理
- KVO是基于runtime機(jī)制實(shí)現(xiàn)的
- 當(dāng)某個(gè)類的屬性對(duì)象第一次被觀察時(shí),系統(tǒng)就會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類,在這個(gè)派生類中重寫基類中任何被觀察屬性的setter 方法。派生類在被重寫的setter方法內(nèi)實(shí)現(xiàn)真正的通知機(jī)制
- 如果原類為Person,那么生成的派生類名為NSKVONotifying_Person
- 每個(gè)類對(duì)象中都有一個(gè)isa指針指向當(dāng)前類,當(dāng)一個(gè)類對(duì)象的第一次被觀察,那么系統(tǒng)會(huì)偷偷將isa指針指向動(dòng)態(tài)生成的派生類,從而在給被監(jiān)控屬性賦值時(shí)執(zhí)行的是派生類的setter方法
- 鍵值觀察通知依賴于NSObject 的兩個(gè)方法: willChangeValueForKey: 和 didChangevlueForKey:;在一個(gè)被觀察屬性發(fā)生改變之前, willChangeValueForKey:一定會(huì)被調(diào)用,這就 會(huì)記錄舊的值。而當(dāng)改變發(fā)生后,didChangeValueForKey:會(huì)被調(diào)用,繼而 observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用。
- 補(bǔ)充:KVO的這套實(shí)現(xiàn)機(jī)制中蘋果還偷偷重寫了class方法,讓我們誤認(rèn)為還是使用的當(dāng)前類,從而達(dá)到隱藏生成的派生類
原理圖
自定義KVO (NSObject+kvo)
頭文件
#import <Foundation/Foundation.h>
@interface NSObject (kvo)
- (void)LYJ_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)LYJ_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context block:(void(^)(id object ,NSDictionary *change))block;
@end
體文件
/*
OC的方法
1.SEL 目錄
2.IMP 頁碼
代碼 內(nèi)容
*/
#import "NSObject+kvo.h"
#import <objc/message.h>
@implementation NSObject (kvo)
//自定義的KVO
-(void)LYJ_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
//1.創(chuàng)建一個(gè)類
//Person LYJKVO_Person
NSString * oldName = NSStringFromClass([self class]);
NSString * newName = [@"LYJKVO_" stringByAppendingString:oldName];
//添加類
//- 1.這個(gè)類繼承誰
//- 2.類名
Class MyClass = objc_allocateClassPair([self class], newName.UTF8String, 0);
//注冊類
objc_registerClassPair(MyClass);
//2.動(dòng)態(tài)修改self的類型!!
object_setClass(self, MyClass);
//3.重寫setName: -- 給子類對(duì)象添加setName:方法!!
class_addMethod(MyClass, @selector(setName:), (IMP)setName, "v@:@");
//4.將觀察者綁定到對(duì)象上面
objc_setAssociatedObject(self, (__bridge const void *)@"objc", observer, OBJC_ASSOCIATION_ASSIGN);
}
-(void)LYJ_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context block:(void (^)(id, NSDictionary *))block
{
}
void setName(id self,SEL _cmd,NSString * newName){
struct objc_super person = {self,class_getSuperclass([self class])};
//修改name屬性!!
objc_msgSendSuper(&person, _cmd,newName);
//拿出觀察者!!
id observer = objc_getAssociatedObject(self, @"objc");
//調(diào)用observer的
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"name":newName},nil);
}
調(diào)用
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person * p = [[Person alloc] init];
//添加觀察者!!
// [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
[p LYJ_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
// [p LYJ_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil block:^(id object, NSDictionary *change) {
//
//
// //實(shí)現(xiàn)!!
//
// }];
// [p LYJ_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil block:^(id object, NSDictionary *change) {
// //說先!!
// }];
//
// [p LYJ_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil block:^(id object, NSDictionary *change) {
//
// }];
//
// [p LYJ_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil block:^(id object, NSDictionary *change) {
//
// }];
//
// [p LYJ_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil block:^(id object, NSDictionary *change) {
//
// }];
//
// [p LYJ_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil block:^(id object, NSDictionary *change) {
//
// }];
_p = p;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
static int a = 0;
a++;
self.p.name = [NSString stringWithFormat:@"%d",a];
// NSLog(@"%@",self.p.name);
}
@end
調(diào)用可以看到
這里寫圖片描述
此時(shí)person為LYJKVO_PERSON 為person的子類