1. KVO原理
2. runtime自定義KVO
3. runtime給分類添加關(guān)聯(lián)屬性
- 我們注冊(cè)監(jiān)聽(tīng)的時(shí)候,會(huì)對(duì)注冊(cè)者動(dòng)態(tài)的創(chuàng)建一個(gè)子類對(duì)象,然后底層找方法的的isa指針就變成指向新創(chuàng)建的子類對(duì)象。當(dāng)改變注冊(cè)對(duì)象某個(gè)屬性的時(shí)候,就重寫(xiě)屬性的set方法來(lái)進(jìn)行監(jiān)聽(tīng)。這么說(shuō)可能理解上不是很明白,下面我們結(jié)合代碼來(lái)分析:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (KVO)
@property (nonatomic, strong) NSObject *test;
@property (nonatomic, strong) NSTimer *timer;
- (void)kvo_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end
NS_ASSUME_NONNULL_END
/***********************************************/
#import "NSObject+KVO.h"
#import <objc/message.h>
// timer 與 test 是runtime添加關(guān)聯(lián)屬性代碼, 與自定義kvo無(wú)關(guān)
static const char *key_test = "test";
static const char *key_observer = "objc";
static const char *key_timer = "timer";
@implementation NSObject (KVO)
- (void)kvo_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"KVC_%@", 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);
// (__bridge const void *)@"objc"
object_setClass(self, newClass);
objc_setAssociatedObject(self, key_observer, observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
Class nclass = NSClassFromString(@"KVO_ViewController");
}
- (NSObject *)test {
NSLog(@"分類添加屬性值test_get:%@",objc_getAssociatedObject(self, key_test));
return objc_getAssociatedObject(self, key_test);
}
- (void)setTest:(NSObject *)test {
NSLog(@"分類添加屬性值test_set前:%@",objc_getAssociatedObject(self, key_test));
objc_setAssociatedObject(self, key_test, test, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
NSLog(@"分類添加屬性值test_set后:%@",objc_getAssociatedObject(self, key_test));
}
- (void)setTimer:(NSTimer *)timer {
objc_setAssociatedObject(self, key_timer, timer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSTimer *)timer {
return objc_getAssociatedObject(self, key_timer);
}
void setAge(id self, SEL _cmd, int age) {
//保存當(dāng)前類
Class myclass = [self class];
//將self的isa指針指向父類
object_setClass(self, class_getSuperclass([self class]));
//調(diào)用父類
objc_msgSend(self,@selector(setAge:),age);
//拿出觀察者 (__bridge const void *)@"objc"
id objc = objc_getAssociatedObject(self, key_observer);
//通知觀察者
objc_msgSend(objc,@selector(observeValueForKeyPath:ofObject:change:context:),@"age",self,@{},nil);
//改為子類
object_setClass(self, myclass);
}
@end
#import "ViewController.h"
#import "NSObject+KVO.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// [self addObserver:(nonnull NSObject *) forKeyPath:(nonnull NSString *) options:(NSKeyValueObservingOptions) context:(nullable void *)];
self.view.backgroundColor = [UIColor whiteColor];
[self kvo_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.age = 20;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"自定義kvo監(jiān)聽(tīng)到屬性改變");
}
- (void)setAge:(int)age {
_age = age;
NSLog(@"方法沒(méi)有被覆蓋:%s", __func__);
}
@end
1. 動(dòng)態(tài)添加一個(gè)當(dāng)前類的子類
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [@"AWKVO_" stringByAppendingString:oldClassName];
const char * newName = [newClassName UTF8String];
Class myclass = objc_allocateClassPair([self class], newName, 0);
//添加setter方法,相當(dāng)于重寫(xiě)setter方法, "v@:i" 含義 @: id : SEL v : void
OC(消息發(fā)送機(jī)制),方法由兩部分組成,方法編號(hào)@selector和方法實(shí)現(xiàn)(imp方法指針),先找方法編號(hào)再得到方法的指針,再執(zhí)行方法的代碼塊。
class_addMethod(myclass, @selector(setAge:), (IMP)setAge, "v@:i");
//注冊(cè)新添加的這個(gè)類
objc_registerClassPair(myclass);
//修改被觀察這的isa指針,isa指針指向Person類改成指向myclass這個(gè)類
object_setClass(self, myclass);
//將觀察者的屬性保存到當(dāng)前類里面去
objc_setAssociatedObject(self, (__bridge const void *)@"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//相當(dāng)于重寫(xiě)父類的方法
void setAge(id self, SEL _cmd, int age) {
//保存當(dāng)前類
Class myclass = [self class];
//將self的isa指針指向父類
object_setClass(self, class_getSuperclass([self class]));
//調(diào)用父類
objc_msgSend(self, @selector(setAge:),age);
//拿出觀察者
objc_getAssociatedObject(self, (__bridge const void *)@"objc");
//通知觀察者
objc_msgSend(objc,@selector(observeValueForKeyPath:ofObject:change:context:),self,age,nil,nil);
//改為子類
object_setClass(self, myclass);
}
這樣就可以回調(diào)到ViewContoller中的監(jiān)聽(tīng)方法observeValueForKeyPath:ofObject:change:context:中去,而且相關(guān)的值也傳遞過(guò)去了。
總結(jié)自定義一個(gè)KVO思路:
1.自定義一個(gè)類,繼承 [self class]的一個(gè)子類
2.重寫(xiě)父類的屬性setter方法
3.調(diào)用observeValueForKeyPath:ofObject:change:context:方法,回調(diào)到ViewController中去。
- 附加:
- 蘋(píng)果為什么要用子類(就是C語(yǔ)言創(chuàng)建的那個(gè)子類)監(jiān)聽(tīng)setter方法,而不用分類(Person+AWKVO)呢?
- 回答:原因是當(dāng)你用分類監(jiān)聽(tīng)setter方法的時(shí)候,Person類中setter方法就不會(huì)走了,這樣不好,所以蘋(píng)果使用了子類監(jiān)聽(tīng)setter方法。