iOS-runtime自定義KVO----分類添加關(guān)鍵屬性



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方法。
?著作權(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)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,667評(píng)論 1 32
  • 摘要:這篇文章首先介紹KVO的基本用法,接著探究 KVO (Key-Value Observing) 實(shí)現(xiàn)機(jī)制,并...
    暮年古稀ZC閱讀 1,145評(píng)論 1 6
  • 設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡(jiǎn)要敘述? 設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的邏輯去處理某一種類型的...
    卑微的戲子閱讀 684評(píng)論 0 1
  • 1.設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡(jiǎn)要敘述? 設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的邏輯去處理某一種類...
    司馬DE晴空閱讀 1,478評(píng)論 0 7
  • 今日體驗(yàn):兩天的出游結(jié)束了,以前一直沒(méi)有帶著老婆孩子出去玩,這次帶著他們一起出去玩特別開(kāi)心,一家人特別開(kāi)心,謝謝公...
    xiebo閱讀 122評(píng)論 0 0

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