iOS OC的Runtime詳解

方法的實質

在OC中,方法的實質其實是兩部分組成:

1.方法的代號(SEL),

2.方法的實現(xiàn)(IMP),

對象調用方法,實際上就是一個發(fā)送消息的過程.

比如[person eat];其實等價于:

objc_msgSend(person, @selector(eat));

objc_msgSend(person, NSSelectorFromString(@"eat"));

objc_msgSend(person, sel_registerName("eat"));

再比如我們最熟悉的初始化方法

Person *person = [[Person alloc]init];

在底層其實是這樣實現(xiàn)的:

Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));

ps:為能編譯過需要把 Buid Setting? msg 設置為NO并且導入<objc/runtime.h>

Runtime的作用

上面其實就是調用了蘋果提供的RuntimeAPI,即程序運行時.對于我們開發(fā)者來講,其實主要有3個用處:

1.在程序運行的過程中,動態(tài)的創(chuàng)建一個類

2.在程序運行的過程中,動態(tài)的為某個類添加/修改方法/屬性

3.在程序運行的過程中,動態(tài)的遍歷類的所有方法和成員變量

下面其實都是runtime這些用法的實現(xiàn).

方法的"懶加載"

首先我們知道,NSObject有如下兩個方法:

+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;

前者是當類調用了沒有實現(xiàn)的(就是缺少sel/imp)類方法時調用,后者則是調用了未實現(xiàn)的實例方法時調用.

例如:上面的Person * p = [[Person alloc]init];

我并沒有實現(xiàn)run:方法,但是當我

?? [p performSelector:@selector(run:) withObject:@"100"];

程序當然毫無懸念的崩潰了??.

然而現(xiàn)在操蛋的需求是,讓person表面上在跑,實際上在飛,程序還不能崩...這怎么可能!!!!!!?

但是,為了實現(xiàn)需求,我突然想到之前提到的方法:

#import "Person.h"

@implementation Person

//當類調用了沒有實現(xiàn)的實例方法,會到這來

+ (BOOL)resolveInstanceMethod:(SEL)sel{

//這是添加方法實現(xiàn),類似懶加載

//prama:1.類,2.方法編號SEL,3.方法實現(xiàn)IMP(函數(shù)指針),4.返回值類型(可以為"")

class_addMethod(self,? sel, (IMP)fly, "v@:@");

return [super resolveInstanceMethod:sel];

}

void fly(id self,SEL _cmd,NSString* distance){

NSLog(@"我就想飛!飛了%@米",distance);

};

以上代碼的原理是:

我們調用run:方法其實是發(fā)消息:

objc_msgSend(p, @selector(run:), @"100");

所以實現(xiàn)fly函數(shù)時,要接收(p, @selector(run:), @"100")這3個參數(shù)

這時就實現(xiàn)了"我就想飛!飛了100米",因為fly是當我們調用時才去加載的,類似于屬性的懶加載,故而我們可以說是方法的"懶加載"

ps:我們可以發(fā)現(xiàn)OC方法調用時會默認傳遞兩個參數(shù),id self 和 方法代號 sel.

Hook(鉤子)

runtime最常見的用法,實現(xiàn)方法的欺騙,直接上代碼:

//在load里下鉤子(load = 編譯前加載)

+ (void)load{

//方法欺騙(替換)

//本來的方法

Method sysMethod = class_getClassMethod([NSURL class], @selector(URLWithString:));

//自定義的方法

Method bbMethod = class_getClassMethod([NSURL class], @selector(BBURLWithString:));

//交換

method_exchangeImplementations(sysMethod, bbMethod);

}

//改URLWithString:方法,能判斷空的URL,如果為空返回百度 (交換后如果再掉系統(tǒng)方法會遞歸奔潰 所以需要寫注釋)

+ (instancetype)BBURLWithString:(NSString *)URLString{

NSURL *url = [NSURL BBURLWithString:URLString];

if (url == nil || [URLString isEqualToString:@""]) NSLog(@"url 為 空");

return [NSURL URLWithString:@"http://www.baidu.com"];

}

return url;

}

KVO

我們都知道,一個KVO的實現(xiàn),實際上就是觀察屬性的setter方法,因此我們實現(xiàn)一個KVO需要幾個步驟:

1.動態(tài)派生 類的子類(NSKVONotyfing_person)

2.重寫setter方法

3.方法中

[self willChangeValueForKey:@"name"]

[super setName:name];

[self didChangeValueForKey:@"name"];

未完待續(xù)................................

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容