iOS下Runtime的API使用

前言
網(wǎng)上關(guān)于runtime的資源也有很多,類和方法具體的實(shí)現(xiàn)等等,我就不再重述了。我將通過(guò)使用一些runtime提供的api來(lái)說(shuō)明runtime的部分功能。

源碼中大段的英文注釋說(shuō)明我就不貼了,我根據(jù)我的理解簡(jiǎn)述參數(shù)、返回值和注意點(diǎn)

代碼地址

一、獲取已注冊(cè)(已定義類)的列表

OBJC_EXPORT int objc_getClassList(Class *buffer, int bufferCount)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);

參數(shù)

buffer : 一個(gè)Class類型的數(shù)組的指針;執(zhí)行這個(gè)函數(shù)后,這個(gè)數(shù)組里的指針各指向一個(gè)class的定義,如果bufferCount比總數(shù)小,那數(shù)組中的指針個(gè)數(shù)就以bufferCount為準(zhǔn)。你可以傳入一個(gè)NULL,來(lái)獲取所有類定義

bufferCount: 你已經(jīng)分配在內(nèi)存空間的指針的個(gè)數(shù)。如果這個(gè)值比已注冊(cè)類的值小,這個(gè)函數(shù)會(huì)返回一個(gè)任意已注冊(cè)類的子集。

返回值: 一個(gè)已注冊(cè)類的總數(shù)

注意點(diǎn):通過(guò)這個(gè)函數(shù)你不能確保返回的類的數(shù)目 就是所有繼承NSObject的類的數(shù)目

這個(gè)類返回的是個(gè)數(shù)目,但是如果我們要拿到每個(gè)類呢,或者是某個(gè)類的所有子類呢?
我們先來(lái)嘗試獲取NSObject的所有子類
由于這個(gè)函數(shù)執(zhí)行后可以獲取到每個(gè)class的定義,那么我們可以先申請(qǐng)一塊內(nèi)存,存放所有class的定義,然后再統(tǒng)統(tǒng)塞到一個(gè)NSArray里就可以獲取到所有類的定義了。

先定義一個(gè)Class數(shù)組的指針,以及兩個(gè)整型變量:

Class *class = NULL;
int count, size;

接下來(lái)申請(qǐng)內(nèi)存,存放所有類定義的指針,這里要做一次do循環(huán),原因是你不能確保你一次申請(qǐng)的內(nèi)存空間足夠。

do
{
        //  先通過(guò)傳入NULL和0來(lái)取出所有類的個(gè)數(shù)
        count = objc_getClassList(NULL, 0);  
        //  重新開(kāi)辟一塊內(nèi)存,尺寸是 類指針的大小*總數(shù)
        class = (__unsafe_unretained Class *)realloc(class, count *sizeof(*class));  
        //  再取一次已分配在內(nèi)存中的指針的個(gè)數(shù)
        size = objc_getClassList(class, count);
} while(size != count);    // 判斷已分配的數(shù)量是否等于count,如果不等于就再來(lái)一次

//  經(jīng)過(guò)幾次驗(yàn)證 這個(gè)do循環(huán)一般只執(zhí)行一次就命中了。

接下來(lái)我們要做的是拿到Class數(shù)組中的指針,和NSObject做比較,再存入想獲取的數(shù)組中。因?yàn)樽⒁恻c(diǎn)中也說(shuō)了,這些并不都是繼承NSObject的類。比如NSLeafProxy,什么鬼,反正我沒(méi)用過(guò)。

    NSMutableArray *array = [NSMutableArray array];
    for(int i = 0; i < count; i++)
    {
        // 拿到一個(gè)候選者
        Class candidate = class[i];
        // 設(shè)置一個(gè)superclass,先是等于候選者本身
        Class superclass = candidate;
        // 當(dāng)這個(gè)superclass為空跳出循環(huán)
        while(superclass)
        {
            // 循環(huán)內(nèi)我們判斷這個(gè)superclass是否是NSObject class
            if(superclass == [NSObject class])
            {
                // 如果是則把候選者添加到數(shù)組中,跳出循環(huán)
                [array addObject: candidate];
                break;
            }
            // 如果不是,我們?nèi)カ@取到他的父類后再進(jìn)入循環(huán)做判斷
            superclass = class_getSuperclass(superclass);
        }
    }

最后必須記得,釋放掉Class這個(gè)數(shù)組所分配的內(nèi)存空間

free(class);

如果我們想查找我們自己定義的類的子類,那么我們可以通過(guò)修改if的判斷條件即可。

if (superclass == [自定義類 class])

測(cè)試:有一個(gè)繼承自NSObject的類CowObject,有兩個(gè)繼承自Cow的類,Bull和Buffalo,將函數(shù)中的自定義類換成CowObject,數(shù)組內(nèi)容如圖:

161210_01.png

需要注意的一點(diǎn),這個(gè)方法在查找子類的過(guò)程中,會(huì)先查到所有已定義的類,所以是個(gè)相對(duì)消耗比較大的操作。我在我們的項(xiàng)目中測(cè)試了一下,還算在可接受范圍內(nèi)。具體消耗的時(shí)間,要看項(xiàng)目大小,建議看官最好自己測(cè)試一把。

二、創(chuàng)建一個(gè)子類

OBJC_EXPORT Class objc_allocateClassPair(Class superclass, const char *name,  size_t extraBytes) 
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

參數(shù)

superclass : 你所要?jiǎng)?chuàng)建的子類的爸爸,如果傳nil,將創(chuàng)建一個(gè)rootClass

name : 你所要?jiǎng)?chuàng)建的子類的類名

extraBytes : 需要額外分配的字節(jié)數(shù),通常應(yīng)該傳個(gè)0

返回值: 返回一個(gè)新的類,可能是nil,比如你傳的類名已經(jīng)被使用了

注意點(diǎn): 通過(guò)這個(gè)函數(shù)創(chuàng)建一個(gè)新的類,之后你需要調(diào)用其他的方法來(lái)創(chuàng)建類里的方法和變量,當(dāng)你創(chuàng)建完整個(gè)類,需要調(diào)用objc_registerClassPair方法。之后這個(gè)新的類就可以使用了。你可以通過(guò)調(diào)用object_getClass(newClass)來(lái)獲取newClass的元類。實(shí)例方法和實(shí)例變量需要添加到newClass本身,而類方法需要添加到newClass的元類,也就是metaClass。
還有一點(diǎn)要注意的,通過(guò)這個(gè)函數(shù)來(lái)創(chuàng)建的類,則需要調(diào)用void objc_disposeClassPair(Class cls)函數(shù)來(lái)銷(xiāo)毀這個(gè)類。

這個(gè)函數(shù)的使用就比較簡(jiǎn)單了:

+ (Class)createSubClassWithName:(NSString *)className ofSuperClass:(Class)superClass
{
    // className需要轉(zhuǎn)換成C字符串
    Class subClass = objc_allocateClassPair(superClass, [className UTF8String], 0);
    // 注冊(cè)剛才創(chuàng)建的類
    objc_registerClassPair(subClass);

    if (subClass) return subClass;
    else return nil;
}

測(cè)試:
還是第一個(gè)實(shí)驗(yàn)中的類,在CowObject中添加一個(gè)實(shí)例方法yell。調(diào)用上面代碼塊中的方法創(chuàng)建一個(gè)CowObject的子類,并向這個(gè)子類發(fā)送yell消息。結(jié)果正常執(zhí)行yell方法。

三、獲取某一個(gè)類的所有實(shí)例方法

OBJC_EXPORT Method *class_copyMethodList(Class cls, unsigned int *outCount) 

參數(shù)

cls : 你要獲取方法列表的類

outCount : 輸出方法個(gè)數(shù)

返回值: 返回一個(gè)method數(shù)組

注意點(diǎn): 這個(gè)方法會(huì)返回指定類中,所有的實(shí)例方法,包括你重寫(xiě)的方法,比如你重寫(xiě)了description方法,或者init方法。并且,還包括這個(gè)類中的所有屬性的getter&setter方法。
如果你想獲取一個(gè)類中所有的類方法,你傳入的cls應(yīng)該是一個(gè)元類,可以這樣用:

class_copyMethodList(object_getClass(cls), &count)

如果你想獲取的方法,可能是由superclass來(lái)實(shí)現(xiàn)的,那你獲取方法的時(shí)候要用
class_getInstanceMethod 或者 class_getClassMethod

實(shí)現(xiàn)也很簡(jiǎn)單:

+ (NSArray *)obtainMethodList:(Class)currentClass
{
    unsigned int methodCount = 0;
    NSMutableArray *methodArr = [NSMutableArray array];
    // 這個(gè)方法會(huì)返回指定類中,所有實(shí)例方法,包括重寫(xiě)的方法,例如你重寫(xiě)了init;
    // 并包括所有屬性的getter&setter方法
    Method *methods = class_copyMethodList(currentClass, &methodCount);
    for (int i = 0; i < methodCount; i++)
    {
        Method temp = methods[i];
        // 這里我們用了method_getName方法來(lái)獲取這個(gè)方法SEL
        SEL sel = method_getName(temp);
        NSString *methodName = NSStringFromSelector(sel);
        [methodArr addObject:methodName];
    }
    // 必須要手動(dòng)釋放掉之前創(chuàng)建的methods數(shù)組
    free(methods);

    return [methodArr copy];
}

測(cè)試

實(shí)現(xiàn)一個(gè)類,該類包含一個(gè)屬性、新增一個(gè)實(shí)例方法,并重寫(xiě)了一個(gè)實(shí)例方法

#import "TestMethod.h"

@interface TestMethod ()

@property (nonatomic, assign) Method method;

@end

@implementation TestMethod

- (instancetype)initWithMethod:(Method)method
{
    self = [super init];
    if (self)
    {
        _method = method;
    }
    return self;
}

- (NSString *)description
{
    SEL selName = method_getName(_method);
    return NSStringFromSelector(selName);
}

@end

如果正常,方法列表中應(yīng)該包含:屬性的setter&getter,加上新增和重寫(xiě)的,一共是4個(gè)方法。我們用實(shí)現(xiàn)的方法調(diào)用這個(gè)類來(lái)試一下:

NSArray *methodList = [Tools obtainMethodList:[TestMethod class]];
NSLog(@"methodList: %@", methodList);

輸出結(jié)果:


methodList.png

總結(jié):
runtime給我們提供了很多很靈活的方法,但是用好這些方法也要非常小心,有時(shí)候結(jié)果會(huì)是非常出乎意料的。個(gè)人對(duì)底層也比較感興趣,但是水平有限,所以還是希望大家能多多指出不足,互相交流,共同進(jìn)步!

參考
Mike Ash的runtime代碼(MRC環(huán)境)
runtime源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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