iOS 開發(fā)-一篇短文教你快速掌握 runtime 和 KVO

本文涉及到兩個(gè)面試中經(jīng)常被問到的知識(shí)點(diǎn),runtime 和 KVO。希望通過本文將兩個(gè)知識(shí)點(diǎn)結(jié)合起來一起學(xué)習(xí),加深印象。

Runtime 經(jīng)常被用來做什么?

  • 動(dòng)態(tài)添加屬性
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy) // 給某個(gè)對(duì)象添加一個(gè)屬性
  • 動(dòng)態(tài)添加類
objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, 
                       size_t extraBytes)  // 生成類
objc_registerClassPair(Class _Nonnull cls)  // 注冊(cè)類
  • 動(dòng)態(tài)添加方法
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types)  // 添加方法
  • 交換方法實(shí)現(xiàn)
void method_exchangeImplementations(Method m1, Method m2);

KVO的內(nèi)部實(shí)現(xiàn)原理?

當(dāng)你觀察一個(gè)對(duì)象時(shí),也就是執(zhí)行方法- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;后,一個(gè)新的類會(huì)動(dòng)態(tài)被創(chuàng)建。這個(gè)類繼承自該對(duì)象的原本的類,并重寫了被觀察屬性的 setter 方法。自然,重寫的 setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后,通知所有觀察對(duì)象值的更改。最后把這個(gè)對(duì)象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類是什么 ) 指向這個(gè)新創(chuàng)建的子類,對(duì)象就神奇的變成了新創(chuàng)建的子類的實(shí)例。
原來,這個(gè)中間類,繼承自原本的那個(gè)類。不僅如此,Apple 還重寫了 -class 方法,企圖欺騙我們這個(gè)類沒有變,就是原本那個(gè)類。

如圖可以看到 isa 指針的改變:

添加觀察者之前.jpg
添加觀察者之后.jpg

通過 runtime 實(shí)現(xiàn) KVO

1.創(chuàng)建 User 類,繼承于 NSObject。該類包含一個(gè)屬性 name。
2. 給 NSObject添加一個(gè)分類,NSObject+KVO。模仿系統(tǒng)方法,添加一個(gè)方法- (void)xy_addObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
3.實(shí)現(xiàn)分類方法:


#import "NSObject+KVO.h"
#import <objc/message.h>

@implementation NSObject (KVO)

- (void)xy_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
    // 獲取當(dāng)前類名
    NSString *oldClassName = NSStringFromClass(self.class);
    // 給新類取名
    NSString *newClassName = [@"xykvo_" stringByAppendingString:oldClassName];
    
    const char *name = [newClassName UTF8String];
    
    // 定義新類
    Class myclass = objc_allocateClassPair(self.class, name, 0);
    
    // 注冊(cè)這個(gè)類
    objc_registerClassPair(myclass);
    
    // 改變 isa 指針
    object_setClass(self, myclass);
    
    // 添加 setName 方法 (重寫)
    class_addMethod(myclass, @selector(setName:), (IMP)setName, "v@:@"); 
 
    // 保存觀察者
    objc_setAssociatedObject(self, @"wxy", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

void setName(id self, SEL _cmd, NSString *newName) { // 所有 OC 方法的調(diào)用都會(huì)傳遞兩個(gè)隱式參數(shù)   哪個(gè)對(duì)象的哪個(gè)方法 _cmd文檔就這么寫
    NSLog(@"%@-%@-%@", self,NSStringFromSelector(_cmd),newName);
    
    // 拿到當(dāng)前類 Class
    id class = [self class];
    
    // 將 self 類型轉(zhuǎn)換為父類,改回 isa 指針
    object_setClass(self, class_getSuperclass([self class]));
    
    // 調(diào)用父類的 setName 方法
    objc_msgSend(self, @selector(setName:), newName);
    
    // 拿出觀察者
    id observer = objc_getAssociatedObject(self, @"wxy");
    
    // 通知外界
    objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), newName,nil ,nil );
    NSLog(@"%@", [self class]);
    // 改回子類
    object_setClass(self, class);
}



#import "ViewController.h"
#import "User.h"
#import "NSObject+KVO.h"

@interface ViewController ()

@property (nonatomic, strong) User *user;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    User *user  = [[User alloc] init];
 
    [user xy_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    
    _user  = user;
}


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    NSLog(@"%@  %@", keyPath, self.user.name);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    _user.name = @"??????";
}


本文的目的在于熟悉 runtime 的基本使用,了解 KVO 的內(nèi)部實(shí)現(xiàn)原理。代碼很少,但是值得品味。

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,106評(píng)論 0 9
  • 喜歡就關(guān)注我唄! 1.設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡(jiǎn)要敘述? 設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的...
    iOS白水閱讀 1,176評(píng)論 0 2
  • 寫在前面 程序設(shè)計(jì)語言中有各種各樣的設(shè)計(jì)模式(pattern)和與此對(duì)應(yīng)的反設(shè)計(jì)模式(anti-pattern),...
    Frankxp閱讀 5,020評(píng)論 0 23
  • 一般做法 優(yōu)雅做法 優(yōu)雅做法-升級(jí)版
    翻這個(gè)墻閱讀 4,845評(píng)論 0 4
  • 20170906,5:00,晴,西安 【學(xué)習(xí)易效能時(shí)間管理第十一講】 【行動(dòng)】一次只做一件事情 比爾·蓋茨和巴菲特...
    渺塵03閱讀 739評(píng)論 3 3

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