runtime講哪些東西?
runtime是很寬泛的概念,通常我們在講runtime的時候大多側(cè)重以下兩方面:
基于Class、Object的結(jié)構(gòu)模型講解。
實(shí)踐中基于runtime的api應(yīng)用,這里講的最多的就是基于method swizzling來實(shí)現(xiàn)AOP。
我遇到的問題就是與Class、Object的結(jié)構(gòu)模型相沖突的,所以我們今天要討論的是前者。
runtime是開源的,大家要想了解細(xì)節(jié)還是要大概的看看源碼
使用runtime的api要引入頭文件:
#import <objc/runtime.h>
先從runtime源碼說起
我們先從runtime源碼開始了解一些本質(zhì)上的東西。
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
類的本質(zhì)就是結(jié)構(gòu)
typedef struct objc_class *Class;
Class的本質(zhì)就是代表類的結(jié)構(gòu)體的指針
struct objc_object {
Class isa
}
typedef struct objc_object *id
id也是一個結(jié)構(gòu)體指針
@interface NSObject <NSObject> {
Class isa
}
NSObject本質(zhì)上也是結(jié)構(gòu)
這些都是runtime中的源碼,沒什么好說的,拿出來就是要幫助大家加深對runtime的理解,下面來說說objc_class結(jié)構(gòu)里的isa和super_class之間的關(guān)系,然后深入理解一下Class,這與我遇到的問題是有關(guān)系的。其實(shí)runtime還有很多重要的概念,比如SEL、IMP、Method等...由于和我們今天討論的問題沒有關(guān)系,這里不再展開說明了,這幾個概念都與消息轉(zhuǎn)發(fā)關(guān)系密切,感興趣大家可以看源碼。
這里介紹幾個runtime中的方法,還是看runtime源碼:
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
object_getClass()就是順著isa的指向鏈找到對應(yīng)的類,我們一會要驗證這個isa的指向鏈?zhǔn)欠衽c上面圖中是一致的,就是用這個方法。
與之相關(guān)還有一個方法:object_setClass(),我們可以用該方法,簡單的來看一下runtime的強(qiáng)大,它可以動態(tài)改變類。首先要知道我們在NSLog的時候用的%@打印對象的時候其實(shí)是調(diào)用該類的description方法,而且我們還知道,NSArray對象的description會把每個array里的元素都打印出來,而NSObject對象的description就僅僅打印類名和指針,下面通過一小段代碼看看runtime的強(qiáng)大。
NSArray *tempObj = @[@"hello", @"erliangzi"];
NSLog(@"tempObj:%@", tempObj);
object_setClass(tempObj, [NSObject class]);
NSLog(@"tempObj:%@", tempObj);
2016-02-02 23:56:22.905 TimerDemo[1104:54722] tempObj:(
hello,
erliangzi
)
2016-02-02 23:56:22.906 TimerDemo[1104:54722] tempObj:<NSObject: 0x7ff580d06140>
是不是很不可思議?runtime就是這么強(qiáng)大!
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
這是NSObject類里實(shí)例方法class與類方法class的實(shí)現(xiàn),這里再強(qiáng)調(diào)一下:類方法是在meta class里的,類方法就是把自己返回,而實(shí)例方法中是返回實(shí)例isa的類,我們要驗證這個isa的指向鏈的時候不能用這種方法,千萬記住,為什么,一會說明。
看代碼:
#import <objc/runtime.h>
#import "Person.h"
Person *obj = [Person new];
NSLog(@"instance :%p", obj);
NSLog(@"class :%p", object_getClass(obj));
NSLog(@"meta class :%p", object_getClass(object_getClass(obj)));
NSLog(@"root meta :%p", object_getClass(object_getClass(object_getClass(obj))));
NSLog(@"root meta's meta :%p", object_getClass(object_getClass(object_getClass(object_getClass(obj)))));
NSLog(@"---------------------------------------------");
NSLog(@"class :%p", [obj class]);
NSLog(@"meta class :%p", [[obj class] class]);
NSLog(@"root meta :%p", [[[obj class] class] class]);
NSLog(@"root meta's meta :%p", [[[[obj class] class] class] class]);
Log輸出:
2016-02-02 18:06:11.443 TimerDemo[1718:248402] instance :0x7fc792530f20
2016-02-02 18:06:11.444 TimerDemo[1718:248402] class :0x10ae0e178
2016-02-02 18:06:11.444 TimerDemo[1718:248402] meta class :0x10ae0e150
2016-02-02 18:06:11.444 TimerDemo[1718:248402] root meta :0x10b66a198
2016-02-02 18:06:11.444 TimerDemo[1718:248402] root meta's meta :0x10b66a198
2016-02-02 18:06:11.444 TimerDemo[1718:248402] ---------------------------------------------
2016-02-02 18:06:11.444 TimerDemo[1718:248402] class :0x10ae0e178
2016-02-02 18:06:11.444 TimerDemo[1718:248402] meta class :0x10ae0e178
2016-02-02 18:06:11.444 TimerDemo[1718:248402] root meta :0x10ae0e178
2016-02-02 18:06:11.444 TimerDemo[1718:248402] root meta's meta :0x10ae0e178
分析:
注:Person是一個繼承自NSObject的普通類,里面有個name屬性。
我們發(fā)現(xiàn)調(diào)用class方法的方式不能得到isa的指向鏈,但是第一次調(diào)用是正確的(class的輸出都是0x10ae0e178),為什么?原因就是上面貼出來的class源碼中,我們第一次調(diào)用的class是實(shí)例方法,會返回isa的類,但是第二次開始調(diào)用的就是類方法,返回的是本身,所以還是0x10ae0e178,以后無論怎么調(diào)用都是執(zhí)行的類方法,返回的都是本身,所以,用class方法是得不到isa指向鏈的。
用object_getClass()驗證了我們Class、Object結(jié)構(gòu)模型理論是對的,我們這里特意的打印了root meta class 的isa,發(fā)現(xiàn)果然指向是自己(0x10b66a198)。
從打印結(jié)果我們能看到,類也是對象,meta類也是對象,都占有一塊內(nèi)存,而且我們會發(fā)現(xiàn)類對象、meta類對象、root meta類對象的指針都是用9位16進(jìn)制數(shù)表示,而實(shí)例對象是用12位16進(jìn)制數(shù)表示(這里用的是64位模擬器),為什么這些類對象的指針位數(shù)少?因為它們存在于段上,并不在棧或者堆上,黑魔法那篇文章說過段內(nèi)存的事情。也就是說可以把這些類對象理解成單利,這是很重要的一點(diǎn),希望大家理解,這一點(diǎn)可以讓我們天馬行空的想很多,比如可不可以把網(wǎng)絡(luò)請求寫在類對象里,嫩不能用類對象去解決自釋放的問題,等等...這會是很有意思的思考。
我們理解了這個結(jié)構(gòu)模型之后,看看我遇到的問題吧。
問題來了
看代碼:
#import <objc/runtime.h>
NSTimer *timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(test) userInfo:nil repeats:YES];
NSLog(@"instance :%p", timer1);
NSLog(@"class :%p", object_getClass(timer1));
NSLog(@"meta class :%p", object_getClass(object_getClass(timer1)));
NSLog(@"root meta class:%p", object_getClass(object_getClass(object_getClass(timer1))));
NSLog(@"------------------------------");
NSLog(@"[NSTimer class]:%p", [NSTimer class]);
Log輸出:
2016-02-02 18:19:11.643 TimerDemo[1745:255746] instance :0x7fee8bc7a810
2016-02-02 18:19:11.644 TimerDemo[1745:255746] class :0x10ece02c0
2016-02-02 18:19:11.644 TimerDemo[1745:255746] meta class :0x10ece02e8
2016-02-02 18:19:11.644 TimerDemo[1745:255746] root meta class:0x10e895198
2016-02-02 18:19:11.644 TimerDemo[1745:255746] ------------------------------
2016-02-02 18:19:11.644 TimerDemo[1745:255746] [NSTimer class]:0x10ecdfe38
問題來了
為什么[NSTimer class]:0x10ecdfe38與class:0x10ece02c0得到的指針不一樣?
就是說為什么object_getClass(obj)與[OBJ class]返回的指針不同?
[NSTimer class]返回應(yīng)該是類對象,object_getClass(timer1)返回的也應(yīng)該是類對象,上面也說過,可以把類對象理解成單利,為什么指針不同?
如果用Person類做實(shí)驗兩者返回就是相同的,如果用系統(tǒng)其它類做實(shí)驗兩者返回還是不同的,它們本身之間就有矛盾,更重要的是,與我們剛剛理解的結(jié)構(gòu)模型也是矛盾的,如何用這個模型理論去解釋[NSTimer class]返回的這個指針?
感興趣的朋友可以不往下看,自己想想為什么,其實(shí)很簡單,但是沒想到會是這樣的,我當(dāng)時就是這個感受。
答案來了
答案非常簡單,兩個字:類簇
看代碼
NSTimer *timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(test) userInfo:nil repeats:YES];
NSLog(@"instance :%@", timer1);
NSLog(@"class :%@", object_getClass(timer1));
NSLog(@"meta class :%@", object_getClass(object_getClass(timer1)));
NSLog(@"root meta class:%@", object_getClass(object_getClass(object_getClass(timer1))));
NSLog(@"------------------------------");
NSLog(@"[NSTimer class]:%@", [NSTimer class]);
代碼沒有變,只是我們這次不打印指針,打印對象的描述:
Log輸出:
2016-02-02 18:31:54.405 TimerDemo[1772:263501] instance :<__NSCFTimer: 0x7ff83841e530>
2016-02-02 18:31:54.405 TimerDemo[1772:263501] class :__NSCFTimer
2016-02-02 18:31:54.405 TimerDemo[1772:263501] meta class :__NSCFTimer
2016-02-02 18:31:54.406 TimerDemo[1772:263501] root meta class:NSObject
2016-02-02 18:31:54.406 TimerDemo[1772:263501] ------------------------------
2016-02-02 18:31:54.406 TimerDemo[1772:263501] [NSTimer class]:NSTimer
我們發(fā)現(xiàn)我們之前結(jié)構(gòu)模型的認(rèn)識沒有錯,之所以矛盾是因為NSTimer是個類簇,它返回的并不是NSTimer對象,而是__NSCFTimer對象!我沒有想到NSTimer也是類簇,我們熟悉的類簇是NSNumber,NSArray,NSDictionary、NSString...這說明大多數(shù)的OC類都是類簇實(shí)現(xiàn)的(連NSTimer也不放過),也說明為什么我們Person類是正常的,因為它不是類簇實(shí)現(xiàn)的。
這里還是簡單的說說類簇的概念吧:一個父類有好多子類,父類在返回自身對象的時候,向外界隱藏各種細(xì)節(jié),根據(jù)不同的需要返回的其實(shí)是不同的子類對象,這其實(shí)就是抽象類工廠的實(shí)現(xiàn)思路,iOS最典型的就是NSNumber。
NSNumber *intNum = [NSNumber numberWithInt:1];
NSNumber *boolNum = [NSNumber numberWithBool:YES];
NSLog(@"intNum :%@", [intNum class]);
NSLog(@"boolNum:%@", [boolNum class]);
2016-02-02 23:15:23.868 TimerDemo[1018:35735] intNum :__NSCFNumber
2016-02-02 23:15:25.027 TimerDemo[1018:35735] boolNum:__NSCFBoolean
這里的numberWithXXXX方法是類工廠返回的其實(shí)并不是NSNumber類,而是各個子類,NSCFNumber、NSCFBoolean。
這和我們上面問題中的NSTimer很像,類方法
scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:并沒有返回NSTimer對象,而是返回了它的子類__NSCFTimer對象。
有人可能要問,如何證明__NSCFTimer就是NSTimer的子類,如何證明類簇是真的?其實(shí)很簡單:
NSLog(@"[NSTimer class] :%p", [NSTimer class]);
NSLog(@"class_getSuperClass:%p", class_getSuperclass([timer1 class]));
2016-02-02 23:22:54.367 TimerDemo[1038:39690] [NSTimer class] :0x109ee4e38
2016-02-02 23:22:54.367 TimerDemo[1038:39690] class_getSuperClass:0x109ee4e38