Runtime淺析

什么是Runtime

  • C語言是一門靜態(tài)語言李请,在編譯階段已確定所有的數(shù)據(jù)類型里烦,函數(shù)方法盯蝴。
  • Objective-C是一門動態(tài)語言,在編譯時是不知道具體的變量類型率寡,函數(shù)方式,是在運行階段才確定相關(guān)類型倚搬,函數(shù)冶共。因此我們可以動態(tài)去修改相關(guān)函數(shù)調(diào)用,變量等,使OC變得更靈活捅僵。
  • Objective-C的運行時機制叫做Runtime家卖。
  • Runtime實際是一個庫,OC通過Runtime去調(diào)用底層C語言方法庙楚。

消息轉(zhuǎn)發(fā)機制

所有的Objective-C方法上荡,在編譯時都是轉(zhuǎn)化為對C方法objc_msgsend()的調(diào)用
1.先來看看OC正常的函數(shù)調(diào)用
先創(chuàng)建一個Test類

// .h文件
@interface Test : NSObject
- (void)run;
- (void)eatFood:(NSString *)food;
+ (void)run;
@end

//.m文件
@implementation Person
- (void)run {
    NSLog(@"Run方法運行");
}
- (void)eatFood:(NSString *)food {
    NSLog(@"吃%@", food);
}
+ (void)run {
    NSLog(@"執(zhí)行了類方法Run");
}
@end

在viewController中調(diào)用Test.h,并在viewDidLoad執(zhí)行

#import "Test.h"
- (void)viewDidLoad {
     [super viewDidLoad];

    Test *obj = [Test new];
    //執(zhí)行run方法
    [obj run];
    //執(zhí)行eat方法
    [obj eatFood:@"水果"];
    //執(zhí)行類方法
    [Test run];
}

2.通過Runtime去調(diào)用方法

#import "Test.h"
//調(diào)用Runtime需要引入對應(yīng)庫
#import <objc/message.h>

- (void)viewDidLoad {
     [super viewDidLoad];

    Test *obj = [Test new];
    objc_msgSend(obj, @selector(run));
    objc_msgSend(obj, @selector(eatFood:), @"水果");
    objc_msgSend([Test class], @selector(run));
}

objc_msgSend中可以傳入多個參數(shù)

  • 第一個參數(shù)為執(zhí)行的對象(類方法,則傳類馒闷,類實際也是一種對象)
  • 第二個參數(shù)為調(diào)用的方法
  • 第三個及后序參數(shù)為可選參數(shù)酪捡,傳入第二個參數(shù)方法需要的參數(shù)
    執(zhí)行結(jié)果如圖:


    運行結(jié)果

    2、注意蘋果是禁止使用objc_msgsend方法的纳账,要使用需要關(guān)閉對應(yīng)檢測Build Setting -> Enable Strict Checking of objc_msgSend Calls改為No


    修改設(shè)置

交換方法

1.交換方法是我們開發(fā)中經(jīng)常運用到逛薇,在很多老得項目中,有大量使用一個老方法疏虫,如果一個又一個去修改會耗費大量的時間永罚,所以可以通過RunTime交換方法,快速替換卧秘。
2.舉個例子

#在原有方法中調(diào)用了[NSURL URLWithString:]
NSURL *url = [NSURL URLWithString:@"www.baidu.com"];
NSLog(@"%@", url);
#如果url中不包含中文呢袱,該方法正常執(zhí)行,但若插入中文翅敌,url會為空
url = [NSURL URLWithString:@"www.baidu.com\中文"];
NSLog(@"%@", url);

3.這時后就需要替換全部URLWithString方法产捞。
我們可以創(chuàng)建一個NSURL的分類,實現(xiàn)一個新的方法NewWithString

@implementation NSURL (url)
+ (instancetype)NewWithString:(NSString *)str {
    NSURL *url = [NSURL URLWithString:str];
    if (!url) {
        NSLog(@"URL為空");
    }
    return url;
}
@end

4.根據(jù)load方法在加載進入OC運行時被執(zhí)行哼御,可以在load方法中實現(xiàn)函數(shù)替換

  • 我們需要用到class_getClassMethod獲取到原有類方法和新的類方法(獲取實例方法則通過class_getInstanceMethod)
  • 通過method_exchangeImplementations來交換兩個方法
+ (void)load {
    //獲取老方法
    Method oldMethod = class_getClassMethod([NSURL class], @selector(URLWithString:));
    //獲取新方法
    Method newMethod = class_getClassMethod([NSURL class], @selector(NewWithString:));
    //交換方法
    method_exchangeImplementations(oldMethod, newMethod);
}
  • 注意:
    如果直接執(zhí)行會陷入死循環(huán)坯临。
    因為在原有方法中,各個函數(shù)的方法實現(xiàn)如圖(ps 圖有點丑恋昼,見諒):



    經(jīng)過修改后變?yōu)椋?/p>


    因為我們在NewWithString執(zhí)行:
NSURL *url = [NSURL URLWithString:str];

它會不斷調(diào)用NewWithString的函數(shù)實現(xiàn)看靠,導致死循環(huán)。需改成:

NSURL *url = [NSURL NewWithString:str];

執(zhí)行結(jié)果如圖:


方法懶加載

節(jié)約性能液肌,我們經(jīng)常用到屬性的懶加載挟炬,函數(shù)方法同樣可以懶加載
1.同樣使用Test.h文件,不定義任何方法
2.我們在viewController直接調(diào)用

objc_msgSend(obj, @selector(lazyMethod));

因為沒有定義方法嗦哆,函數(shù)會直接報錯谤祖。
3.實際上,在沒有找到方法時老速,會執(zhí)行對應(yīng)類的

//對應(yīng)類方法
+ (BOOL)resolveClassMethod:(SEL)sel;
//對應(yīng)實例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel;

4.我們先在Test.m中定義一個需要懶加載的方法

//id self, SEL _cmd為隱式參數(shù)可不寫
int newMethod(id self, SEL _cmd) {
    NSLog(@"執(zhí)行了");
    return 0;
}

5.在發(fā)現(xiàn)方法為需要懶加載的方法時粥喜,將函數(shù)加載進去

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if ([NSStringFromSelector(sel) isEqualToString:@"lazyMethod"]) {
        class_addMethod([Test class], sel, newMethod, "i@:");
    }
    return [super resolveInstanceMethod:sel];
}

class_addMethod有四個參數(shù)
1.對應(yīng)類
2.方法名
3.添加的方法
4.方法需要使用的參數(shù),newMethod的返回值int 對應(yīng)"i", id self對應(yīng)"@",SEL對應(yīng)":"具體對應(yīng)可參考文檔
執(zhí)行結(jié)果如圖:

執(zhí)行結(jié)果

KVO底層實現(xiàn)

kvo原理是創(chuàng)建對應(yīng)類的子類橘券,在子類中重寫set方法额湘,同時修改isa指針指向新創(chuàng)建的類卿吐。
1.創(chuàng)建一個Person類,并添加name屬性用于監(jiān)聽測試锋华。

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end

2.創(chuàng)建NSObject分類嗡官,添加新的監(jiān)聽方法

@interface NSObject (KVO)
- (void)new_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end

3.在ViewController添加監(jiān)聽

- (void)viewDidLoad {
    [super viewDidLoad];
    _p = [Person new];
    NSLog(@"修改前的類%@", [_p class]);
    //使用自定義KVO監(jiān)聽
    [self.p new_addObserver:self forKeyPath:@"name" options:0 context:nil];
    NSLog(@"修改后的類%@", [_p class]);
 }

//name改變后調(diào)用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"監(jiān)聽到了name改變:%@",_p.name);
}

//點擊改變值,觸發(fā)KVO
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    static int i = 0;
    i++;
    _p.name = [NSString stringWithFormat:@"%d", i];
}

4.NSObject分類實現(xiàn)

- (void)new_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
    //self 被觀察者
    //observer 觀察者
    //1毯焕、自定義子類
    //獲取self類名
    NSString *oldClassName = NSStringFromClass([self class]);
    //創(chuàng)建self的子類
    NSString *newClassName = [@"new_" stringByAppendingString:oldClassName];
    const char *newName = [newClassName UTF8String];
    //動態(tài)生成類
    Class newClass = objc_allocateClassPair([self class], newName, 0);
    //注冊類 類加入內(nèi)存中可供調(diào)用
    objc_registerClassPair(newClass);
    SEL s = NSSelectorFromString([NSString stringWithFormat:@"set%@:", [keyPath capitalizedString]]);
    //2衍腥、添加set方法
    class_addMethod(newClass, s, (IMP)setObject, "v:@:@");
    //3、修改isa指針
    object_setClass(self, newClass);
    //保存觀察者對象
    objc_setAssociatedObject(self, "objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

void setObject(id self, SEL _cmd, id newName){
    //1.調(diào)用super的set方法
    id class = [self class];
    //改變self的isa指針
    object_setClass(self, class_getSuperclass(class));
//    objc_msgSend(self, @selector(setName:), newName);
    SEL s = NSSelectorFromString([NSString stringWithFormat:@"set%@:", [objc_getAssociatedObject(self, "keyPath") capitalizedString]]);
    objc_msgSend(self, s, newName);
    NSLog(@"修改完畢");
    //拿到觀察者
    id objc = objc_getAssociatedObject(self, "objc");
    //通知觀察者
    objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:), objc_getAssociatedObject(self, "keyPath"),self, nil, nil);
    //改回子類類型
    object_setClass(self, class);
}

執(zhí)行結(jié)果如圖:


執(zhí)行結(jié)果
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纳猫,一起剝皮案震驚了整個濱河市婆咸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌续担,老刑警劉巖擅耽,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件活孩,死亡現(xiàn)場離奇詭異物遇,居然都是意外死亡,警方通過查閱死者的電腦和手機憾儒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門询兴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人起趾,你說我怎么就攤上這事诗舰。” “怎么了训裆?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵眶根,是天一觀的道長。 經(jīng)常有香客問我边琉,道長属百,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任变姨,我火速辦了婚禮族扰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘定欧。我一直安慰自己渔呵,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布砍鸠。 她就那樣靜靜地躺著扩氢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪爷辱。 梳的紋絲不亂的頭發(fā)上类茂,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天耍属,我揣著相機與錄音,去河邊找鬼巩检。 笑死厚骗,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的兢哭。 我是一名探鬼主播领舰,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼迟螺!你這毒婦竟也來了冲秽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤矩父,失蹤者是張志新(化名)和其女友劉穎锉桑,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窍株,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡民轴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了球订。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片后裸。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖冒滩,靈堂內(nèi)的尸體忽然破棺而出微驶,到底是詐尸還是另有隱情,我是刑警寧澤开睡,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布因苹,位于F島的核電站,受9級特大地震影響篇恒,放射性物質(zhì)發(fā)生泄漏扶檐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一婚度、第九天 我趴在偏房一處隱蔽的房頂上張望蘸秘。 院中可真熱鬧,春花似錦蝗茁、人聲如沸醋虏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颈嚼。三九已至,卻和暖如春饭寺,著一層夾襖步出監(jiān)牢的瞬間阻课,已是汗流浹背叫挟。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留限煞,地道東北人抹恳。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像署驻,于是被迫代替她去往敵國和親奋献。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359