用runtime實(shí)現(xiàn)一個(gè)KVO

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);
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容