1 KVO的原理
當(dāng)一個(gè)類(lèi)的屬性被觀察的時(shí)候,系統(tǒng)會(huì)通過(guò)runtime動(dòng)態(tài)的創(chuàng)建一個(gè)該類(lèi)的派生類(lèi),并且會(huì)在這個(gè)類(lèi)中重寫(xiě)基類(lèi)被觀察的屬性的setter方法,而且系統(tǒng)將這個(gè)對(duì)象的isa指針指向了派生類(lèi),從而實(shí)現(xiàn)了給監(jiān)聽(tīng)的屬性賦值時(shí)調(diào)用的是派生類(lèi)的setter方法。重寫(xiě)的setter方法會(huì)在調(diào)用原setter方法前后,通知觀察對(duì)象值得改變。此外,派生類(lèi)還重寫(xiě)了 dealloc 方法來(lái)釋放資源。

111.png
2動(dòng)手實(shí)現(xiàn)一個(gè)KVO
假設(shè)有一個(gè)Person類(lèi)
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSString *age;
@end
@implementation Person
@end
創(chuàng)建一個(gè)NSObject+MyKVO的分類(lèi),然后再.m文件中加入以下代碼
- (void)my_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
//給新類(lèi)取名字
NSString *currentClassName = NSStringFromClass([self class]);
NSString *createdClassName = [@"MyKVO" stringByAppendingString:currentClassName];
const char * newname = [createdClassName UTF8String];
//用runtime創(chuàng)建一個(gè)類(lèi),創(chuàng)建完類(lèi)要注冊(cè)才能用。第一個(gè)參數(shù)表示新類(lèi)繼承誰(shuí)
Class newClass = objc_allocateClassPair([self class], newname, 0);
objc_registerClassPair(newClass);
//添加一個(gè)name的set方法
class_addMethod(newClass, @selector(setName:), (IMP)setName, "v@:@");
//設(shè)置一個(gè)對(duì)象的類(lèi)(即改編isa指針),也就是說(shuō)當(dāng)前這個(gè)對(duì)象現(xiàn)在是新類(lèi)的實(shí)例
object_setClass(self, newClass);
const void *key = "qwer";
//添加一個(gè)成員變量名字叫objc,值就是觀察者observer。
objc_setAssociatedObject(self, key, observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//我們要在setName調(diào)用super 的set 方法還要通知外界
void setName(id self, SEL _cmd, NSString * newName){
//保存新類(lèi)
id class = [self class];
//先把isa指針指向它的父類(lèi)(就是原來(lái)那個(gè)類(lèi))
object_setClass(self, class_getSuperclass(class));
//讓父類(lèi)調(diào)用setName方法
objc_msgSend(self, @selector(setName:),newName);
//拿到observer
id objc = objc_getAssociatedObject(self, "qwer");
//調(diào)用observer的observeValueForKeyPath方法,參數(shù)1:person本身,2要監(jiān)聽(tīng)的name
objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:),self,@"name",nil,nil);
//改回新類(lèi)類(lèi)型
object_setClass(self, class);
}
此時(shí)在VC中就能監(jiān)聽(tīng)name的改變了
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[Person alloc]init];
[self.person my_addObserver:self forKeyPath:@"name" options:0 context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"監(jiān)聽(tīng)到了%@",_person.age);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
static int a = 1;
a++;
self.person.name = [NSString stringWithFormat:@"%d",a];
}
3 完善
這個(gè)時(shí)候MyKVO只能監(jiān)聽(tīng)name屬性,還不能監(jiān)聽(tīng)其他屬性,比如不能監(jiān)聽(tīng)age,下面修改完善NSObject+MyKVO
//轉(zhuǎn)換被監(jiān)聽(tīng)的keyPath為SEL
-(SEL)convertKeyPathToSEL:(NSString*)keyPath{
NSString *firstUp = [[keyPath uppercaseString]substringToIndex:1];
NSString *otherStr = [keyPath substringFromIndex:1];
NSString *SELString = [[@"set" stringByAppendingString:[firstUp stringByAppendingString:otherStr]]stringByAppendingString:@":"];
return NSSelectorFromString(SELString);
}
- (void)my_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
NSString *currentClassName = NSStringFromClass([self class]);
NSString *createdClassName = [@"MyKVO" stringByAppendingString:currentClassName];
const char * newname = [createdClassName UTF8String];
Class newClass = objc_allocateClassPair([self class], newname, 0);
objc_registerClassPair(newClass);
//添加一個(gè)被監(jiān)聽(tīng)屬性的set方法,IMP是setName
class_addMethod(newClass,[self convertKeyPathToSEL:keyPath], (IMP)setName, "v@:@");
object_setClass(self, newClass);
const void *key = "myObserver";
const void *mykeyPath = "myKeyPath";
objc_setAssociatedObject(self, key, observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, mykeyPath, keyPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//我們要在setName調(diào)用super 的set 方法還要通知外界
void setName(id self, SEL _cmd, NSString * newName){
NSString *keyPath = objc_getAssociatedObject(self, "myKeyPath");
id class = [self class];
object_setClass(self, class_getSuperclass(class));
//讓父類(lèi)調(diào)用set方法
objc_msgSend(self, [self convertKeyPathToSEL:keyPath],newName);
id objc = objc_getAssociatedObject(self, "myObserver");
objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,nil,nil);
object_setClass(self, class);
}