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