iOS:RunTime基本使用與實(shí)際運(yùn)用

一垮抗、RunTime是什么?

定義:RunTime實(shí)際上是一個(gè)庫(kù),這個(gè)庫(kù)使我們可以在程序運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建對(duì)象、檢查對(duì)象梆掸,修改類(lèi)和對(duì)象的方法宰睡。他的作用其實(shí)就是在程序運(yùn)行時(shí)做一些事情租冠。

下面我們來(lái)看看它的常用方法垄惧,前提引入頭文件\color{red}{import <objc/message.h>} #,再去\color{red}{【XCode】->【Build Settings】 -> 【Enable Strict Checking of objc_msgSend Calls】}這個(gè)字段設(shè)置為 NO(默認(rèn)為YES)炒辉,防止編譯器校驗(yàn)語(yǔ)法豪墅。

1:方法交換

Person *person = [[Person alloc] init];
Method m1 = class_getInstanceMethod(person.class, @selector(eat));
Method m2 = class_getInstanceMethod(person.class, @selector(run));
method_exchangeImplementations(m1, m2);

2:動(dòng)態(tài)添加方法

注意使用performSelector: 來(lái)調(diào)用,因?yàn)閷?duì)象在編譯階段是沒(méi)有addMethodTest方法的黔寇。防止編譯不過(guò)

//方法一:
Person *p = [[Person alloc]init];
IMP imp = class_getMethodImplementation(self.class, @selector(test));
Boolean success = class_addMethod(p.class, @selector(addMethodTest), imp, "v@");
[p performSelector:@selector(addMethodTest)];

//方法二:
Person *p = [[Person alloc]init];
void(^testImp)(void) = ^{
     NSLog(@"***q*");
};
IMP imp = imp_implementationWithBlock(testImp);
Boolean success = class_addMethod(p.class, @selector(addMethodTest), imp, "v@");
[p performSelector:@selector(addMethodTest)];

3:動(dòng)態(tài)添加屬性

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, "key_name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
    return (NSString *)objc_getAssociatedObject(self, "key_name");
}

4:遍歷屬性

注意:通過(guò)objc_setAssociatedObject 添加的屬性偶器,是不能在這里發(fā)現(xiàn)的

Person *person = [[Person alloc]init];
unsigned count =0;
Ivar * ivars = class_copyIvarList(person.class, &count);
for (int i =0; i < count; i ++) {
     const char *s = ivar_getName(ivars[i]);
     NSString * property = [NSString stringWithCString:s encoding:NSUTF8StringEncoding];
     NSLog(@" property = %@", property);
}

二、它有什么作用

1缝裤、程序crash檢測(cè)

當(dāng)對(duì)象調(diào)用沒(méi)有實(shí)現(xiàn)的方法屏轰,即在程序運(yùn)行時(shí),Runtime 會(huì)監(jiān)測(cè)程序發(fā)生的意外憋飞,如果有crash發(fā)生霎苗,出于對(duì)用戶的體驗(yàn)考慮,runtime允許在即將crash前榛做,讓開(kāi)發(fā)者做一些事情去挽救APP唁盏,避免crash發(fā)生内狸。其中有三個(gè)階段可以處理crash。
實(shí)際開(kāi)發(fā)中沒(méi)有人傻到會(huì)去調(diào)用沒(méi)有實(shí)現(xiàn)的方法厘擂,如果有當(dāng)我沒(méi)說(shuō)[滑稽]昆淡!,但是當(dāng)項(xiàng)目大了之后刽严,解析數(shù)據(jù)昂灵,版本字段變更,升級(jí)后緩存未清理港庄,都有可能發(fā)生 unrecognized selector sent to…錯(cuò)誤倔既。
這時(shí)可以就可以考慮以下解決方案了!

方案A:Method resolution-方法解析鹏氧,解決

注意:添加方法時(shí):class_addMethod中,實(shí)例方法是[self class]佩谣,類(lèi)方法是objc_getMetaClass(“ViewController”), 因?yàn)轭?lèi)方法最終查找方法實(shí)現(xiàn)是去對(duì)象的元類(lèi)對(duì)象中查找把还,所以添加也是去元類(lèi)對(duì)象中添加

- (void)viewDidLoad {
    [super viewDidLoad];
    //類(lèi)方法
    [ViewController performSelector:@selector(noMethod)];
    //實(shí)例方法
    [self performSelector:@selector(noMethod)];
}
- (void)test {
    NSLog(@"攔截到 %@ crash",NSStringFromSelector(_cmd));
}

//實(shí)例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if ([NSStringFromSelector(sel) isEqualToString:@"noMethod"]) {
        IMP imp = class_getMethodImplementation([self class], @selector(test));
        class_addMethod([self class], sel, imp, "v@");
    }
    return [super resolveInstanceMethod:sel];
}

//類(lèi)方法
+ (BOOL)resolveClassMethod:(SEL)sel {
    if ([NSStringFromSelector(sel) isEqualToString:@"noMethod"]) {
        IMP imp = class_getMethodImplementation([ViewController class], @selector(test));
        class_addMethod(objc_getMetaClass("ViewController"), sel, imp, "v@");
    }
    return [super resolveClassMethod:sel];
}

方案B:Fast forwarding-消息快速轉(zhuǎn)發(fā)

這一步比較簡(jiǎn)單,就是找一個(gè)能實(shí)現(xiàn)該方法的對(duì)象茸俭,讓這個(gè)對(duì)象去實(shí)現(xiàn)該方法吊履;

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([[Person new] respondsToSelector:aSelector]) {
        return [Person new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

方案C:Forwarding-消息慢轉(zhuǎn)發(fā)

當(dāng)然這里的慢是相對(duì)于方案B的,不是說(shuō)真的就是很慢调鬓。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([super methodSignatureForSelector:aSelector] == nil) {
        NSMethodSignature *methodSign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
        return methodSign;
    } else {
        return [super methodSignatureForSelector:aSelector];
    }
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;
    if ([[Person new] respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:[Person new]];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

注意:以上三個(gè)步驟艇炎,效率上 A > B >C,由于對(duì)象會(huì)緩存方法腾窝,如果方案A實(shí)現(xiàn)了缀踪,addMethod,那么下次調(diào)用該方法時(shí)虹脯,會(huì)直接從對(duì)象的method cache 里面直接調(diào)用驴娃,效率更高。A循集,B唇敞,C都實(shí)現(xiàn)了,只會(huì)執(zhí)行A咒彤!

2:實(shí)現(xiàn)NSDictionary 轉(zhuǎn) Model

就是利用runtime 遍歷model實(shí)例對(duì)象的屬性疆柔,然后通過(guò)KVC給這個(gè)屬性賦值。\color{red}{[instance setValue:@"value" forKey:property];}

3:Category中添加屬性

這里需要注意:添加的屬性不是真正這個(gè)屬性镶柱。而是關(guān)聯(lián)對(duì)象旷档。
真正添加屬性如下:

Class cat_cls = objc_allocateClassPair([NSObject class], "OBCat", 0);
        
class_addIvar(cat_cls, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString*));
objc_registerClassPair(cat_cls);
        
id ob_cat = [[cat_cls alloc] init];
[ob_cat setValue:@"tom" forKey:@"name"];
        

添加屬性需要在實(shí)例化之后,注冊(cè)之前添加奸例,即在\color{red}{objc_allocateClassPair}方法和\color{objc_registerClassPair}之間添加彬犯。如果在$\color{red}{objc_registerClassPair}之后添加無(wú)效向楼。還可能崩潰。這里涉及到OC中類(lèi)的成員變量的偏移量, 如果在類(lèi)注冊(cè)之后再addIvar的話會(huì)破壞原來(lái)類(lèi)成員變量正確的偏移量, 這樣的話會(huì)導(dǎo)致你訪問(wèn)的那個(gè)成員變量并不是你想訪問(wèn)的成員變量

4:通用埋點(diǎn)

利用runtime的方法交換谐区,監(jiān)聽(tīng)ViewController的停留時(shí)長(zhǎng)湖蜕,還能做到無(wú)侵入式。Hook生命周期的方法宋列,然后交換方法實(shí)現(xiàn)昭抒,然后在自定義的方法中,調(diào)用外部方法炼杖,并 invoke 原來(lái)生命周期的方法灭返。

詳情請(qǐng)點(diǎn)擊埋點(diǎn)專題

5:KVO

以上說(shuō)的是\color{red}{method-swizzling},而KVO則是\color{red}{isa-swizzling}的一種運(yùn)用;
kvo底層也是通過(guò)runtime來(lái)實(shí)現(xiàn)的坤邪,當(dāng)某個(gè)對(duì)象設(shè)置kvo時(shí)熙含,runtime會(huì)在運(yùn)行時(shí),動(dòng)態(tài)的創(chuàng)建該類(lèi)的子類(lèi)艇纺,并重寫(xiě)set方法怎静,當(dāng)set方法調(diào)用時(shí),會(huì)通知觀測(cè)者黔衡;

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    // key為name蚓聘,開(kāi)啟手動(dòng)通知
    if ([key isEqualToString:@"name"]) {
        return NO;
    }
    return [super automaticallyNotifiesObserversForKey:key];
}

如果\color{red}{automaticallyNotifiesObserversForKey}返回的是NO;那么需要手動(dòng)調(diào)用\color{red}{willChangeValueForKey},或者\color{red}{didChangeValueForKey}觸發(fā)通知觀測(cè)者

//重寫(xiě)set方法;
- (void)setName:(NSString *)name {
    [self willChangeValueForKey:@"name"];
    _name = name;
    [self didChangeValueForKey:@"name"];
}

kvo底層實(shí)現(xiàn) :isa-swizzling

先創(chuàng)建一個(gè)子類(lèi)盟劫,重寫(xiě)其set方法夜牡,然后修改對(duì)象的isa指針,指向子類(lèi)

// "addObserver:forKeyPath:options:context:"方法的實(shí)現(xiàn) 
// 動(dòng)態(tài)創(chuàng)建子類(lèi)
NSString *newName = [@"NSKVONotifying_" stringByAppendingString:NSStringFromClass(object_getClass(self))]; 
NSString *setterName = [[@"set" stringByAppendingString:[[[keyPath uppercaseString] substringToIndex:1] stringByAppendingString:[keyPath substringFromIndex:1]]] stringByAppendingString:@":"];
Class subCls = objc_allocateClassPair(object_getClass(self), [newName UTF8String], 0);
class_addMethod(subCls, NSSelectorFromString(setterName), (IMP)DefaultSetterForKVO, "v@:@");
objc_registerClassPair(subCls);
/// 替換子類(lèi)的isa
object_setClass(self, subCls);

此文我是搬運(yùn)工??侣签,參考原文鏈接:請(qǐng)點(diǎn)擊此處

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末塘装,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子硝岗,更是在濱河造成了極大的恐慌氢哮,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件型檀,死亡現(xiàn)場(chǎng)離奇詭異冗尤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)胀溺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)裂七,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人仓坞,你說(shuō)我怎么就攤上這事背零。” “怎么了无埃?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵徙瓶,是天一觀的道長(zhǎng)毛雇。 經(jīng)常有香客問(wèn)我,道長(zhǎng)侦镇,這世上最難降的妖魔是什么灵疮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮壳繁,結(jié)果婚禮上震捣,老公的妹妹穿的比我還像新娘。我一直安慰自己闹炉,他們只是感情好蒿赢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著渣触,像睡著了一般羡棵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上昵观,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天晾腔,我揣著相機(jī)與錄音,去河邊找鬼啊犬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛壁查,可吹牛的內(nèi)容都是我干的觉至。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼睡腿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼语御!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起席怪,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤应闯,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后挂捻,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體碉纺,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年刻撒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了骨田。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡声怔,死狀恐怖态贤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情醋火,我是刑警寧澤悠汽,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布箱吕,位于F島的核電站,受9級(jí)特大地震影響柿冲,放射性物質(zhì)發(fā)生泄漏茬高。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一姻采、第九天 我趴在偏房一處隱蔽的房頂上張望雅采。 院中可真熱鬧,春花似錦慨亲、人聲如沸婚瓜。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)巴刻。三九已至,卻和暖如春蛉签,著一層夾襖步出監(jiān)牢的瞬間胡陪,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工碍舍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柠座,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓片橡,卻偏偏與公主長(zhǎng)得像妈经,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子捧书,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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