iOS 通過RunTime重寫KVO

KVO原理:當(dāng)一個(gè)對(duì)象被觀察時(shí), 系統(tǒng)會(huì)新建一個(gè)子類NSNotifying_A ,在子類中重寫了對(duì)象被觀察屬性的 set方法, 并且改變了該對(duì)象的 isa 指針的指向(指向了新建的子類) , 當(dāng)屬性的值發(fā)生改變了, 會(huì)調(diào)用子類的set方法, 然后發(fā)出通知

一. 創(chuàng)建NSObject分類, 創(chuàng)建類方法和回調(diào)用的block

typedef void(^IBKVOBlock)(NSDictionary *_Nonnull dictionary);
@interface NSObject (IBKVO)
+ (void)IB_AddObserverWithKeyPath:(NSString *)keypath option:(NSKeyValueObservingOptions)option block:(IBKVOBlock)block;
@end

二. 具體代碼以及注解

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

static const char *IBKVO_getter = "IBKVO_getter";
static const char *IBKVO_setter = "IBKVO_setter";
static const char *IBKVO_block = "IBKVO_block";

@implementation NSObject (IBKVO)

+ (void)IB_AddObserverWithKeyPath:(NSString *)keypath option:(NSKeyValueObservingOptions)option block:(IBKVOBlock)block
{
    //創(chuàng)建子類 默認(rèn)子類格式:'IBKVO'+'Notifying_'+ClassName
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"IBKVONotifying_%@",oldClassName];
    
    //判斷子類是否存在 不存在就創(chuàng)建一個(gè)并注冊(cè)
    Class subClass = objc_getClass(newClassName.UTF8String);
    if (!subClass) {
        subClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        objc_registerClassPair(subClass);
    }
    
    //將keypath 首字母大寫 拼成setValue方法名
    NSString *KeyPath = [[keypath substringToIndex:1] uppercaseString];
    NSString *setKeyPath = [@"set" stringByAppendingString:KeyPath];
    
    //判斷屬性存不存在
    Method setM = class_getInstanceMethod(subClass, NSSelectorFromString(setKeyPath));
    if (!setM) {
        @throw [NSExpression expressionWithFormat:@"屬性不存在"];
    }
    
    SEL setSel = NSSelectorFromString([setKeyPath stringByAppendingString:@":"]);
    
    //添加set方法------
    //先找到本來的set方法的type
    Method setMethod = class_getInstanceMethod([self class], NSSelectorFromString(setKeyPath));
    const char* setTypes = method_getTypeEncoding(setMethod);
    
    //添加自定義的set方法 監(jiān)聽set方法的調(diào)用 從而block回調(diào)
    class_addMethod(subClass, setSel, (IMP)setMethod, setTypes);
    
    //將self指向子類
    object_setClass(self, subClass);
    
    //保存set get方法名
    objc_setAssociatedObject(self, IBKVO_setter, setKeyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, IBKVO_getter, keypath, OBJC_ASSOCIATION_COPY_NONATOMIC);
    //保存block
    objc_setAssociatedObject(self, IBKVO_block, block, OBJC_ASSOCIATION_COPY);
}

void setMethod(id self, SEL _cmd,id newValue)
{
    //獲取getter setter 名
    NSString *setterName = objc_getAssociatedObject(self, IBKVO_setter);
    NSString *getterName = objc_getAssociatedObject(self, IBKVO_getter);
    
    //保存subclass
    Class subClass = [self class];
    
    //self(isa) 指向super
    object_setClass(self, class_getSuperclass(subClass));
    
    //獲取更改前的值 必須讓self指向super才能獲取
    id oldValue = objc_msgSend(self, NSSelectorFromString(getterName));
    //調(diào)用setter 賦給新的值
    objc_msgSend(self, NSSelectorFromString([setterName stringByAppendingString:@":"]),newValue);
    
    //記錄前后改變的值
    NSMutableDictionary *change = [NSMutableDictionary new];
    if (newValue) {
        change[NSKeyValueChangeNewKey] = newValue;
    }
    if (oldValue) {
        change[NSKeyValueChangeOldKey] = oldValue;
    }
    
    //返回給調(diào)用者 類似于delegate
    IBKVOBlock block = objc_getAssociatedObject(self, IBKVO_block);
    if (block) {
        block(change);
    }
    // isa 指向子類
    object_setClass(self, subClass);
}
@end
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涌矢,一起剝皮案震驚了整個(gè)濱河市疙驾,隨后出現(xiàn)的幾起案子矮燎,更是在濱河造成了極大的恐慌吩翻,老刑警劉巖继榆,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸠信,死亡現(xiàn)場(chǎng)離奇詭異舟山,居然都是意外死亡拢锹,警方通過查閱死者的電腦和手機(jī)摩窃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門兽叮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猾愿,你說我怎么就攤上這事鹦聪。” “怎么了蒂秘?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵泽本,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我材彪,道長(zhǎng)观挎,這世上最難降的妖魔是什么琴儿? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮嘁捷,結(jié)果婚禮上造成,老公的妹妹穿的比我還像新娘。我一直安慰自己雄嚣,他們只是感情好晒屎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缓升,像睡著了一般鼓鲁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上港谊,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天骇吭,我揣著相機(jī)與錄音,去河邊找鬼歧寺。 笑死燥狰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的斜筐。 我是一名探鬼主播龙致,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼顷链!你這毒婦竟也來了目代?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤嗤练,失蹤者是張志新(化名)和其女友劉穎榛了,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體潭苞,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忽冻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了此疹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片僧诚。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蝗碎,靈堂內(nèi)的尸體忽然破棺而出湖笨,到底是詐尸還是另有隱情,我是刑警寧澤蹦骑,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布慈省,位于F島的核電站,受9級(jí)特大地震影響眠菇,放射性物質(zhì)發(fā)生泄漏边败。R本人自食惡果不足惜袱衷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望笑窜。 院中可真熱鬧致燥,春花似錦、人聲如沸排截。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)断傲。三九已至脱吱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間认罩,已是汗流浹背箱蝠。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留猜年,地道東北人抡锈。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓疾忍,卻偏偏與公主長(zhǎng)得像乔外,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子一罩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容