為什么object_getClass(obj)與[OBJ class]返回的指針不同

引言

該文章與runtime相關(guān),開(kāi)始并沒(méi)打算寫(xiě),因?yàn)榇笊駛儗?xiě)了好多runtime的文章,分析的都很全面、很深刻,再寫(xiě)也就是班門(mén)弄斧。但還是寫(xiě)了,因?yàn)槲以诳匆粋€(gè)東西的時(shí)候偶爾發(fā)現(xiàn)了object_getClass(obj)與[OBJ class]返回的指針不同,感覺(jué)非常奇怪,因?yàn)樗嵏擦宋覀儗?duì)runtime中類結(jié)構(gòu)模型的認(rèn)識(shí),后來(lái)在網(wǎng)上找了相關(guān)問(wèn)題的答案,發(fā)現(xiàn)并沒(méi)有,所以打算寫(xiě)一篇文章來(lái)和大家說(shuō)說(shuō)這個(gè)問(wèn)題,我會(huì)講一點(diǎn)點(diǎn)runtime,但不會(huì)系統(tǒng)的講,目的就是為了能讓大家理解我說(shuō)的問(wèn)題就可以了。

runtime講哪些東西?

runtime是很寬泛的概念,通常我們?cè)谥vruntime的時(shí)候大多側(cè)重以下兩方面:

1.基于Class、Object的結(jié)構(gòu)模型講解。

2.實(shí)踐中基于runtime的api應(yīng)用,這里講的最多的就是基于method swizzling來(lái)實(shí)現(xiàn)AOP。

我遇到的問(wèn)題就是與Class、Object的結(jié)構(gòu)模型相沖突的,所以我們今天要討論的是前者。

runtime是開(kāi)源的,大家要想了解細(xì)節(jié)還是要大概的看看源碼

使用runtime的api要

#import <objc/runtime.h>

先從runtime源碼說(shuō)起

我們先從runtime源碼開(kāi)始了解一些本質(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也是一個(gè)結(jié)構(gòu)體指針


@interface NSObject <NSObject> {
     Class isa
}

NSObject本質(zhì)上也是結(jié)構(gòu)


這些都是runtime中的源碼,沒(méi)什么好說(shuō)的,拿出來(lái)就是要幫助大家加深對(duì)runtime的理解,下面來(lái)說(shuō)說(shuō)objc_class結(jié)構(gòu)里的isa和super_class之間的關(guān)系,然后深入理解一下Class,這與我遇到的問(wèn)題是有關(guān)系的。其實(shí)runtime還有很多重要的概念,比如SEL、IMP、Method等...由于和我們今天討論的問(wèn)題沒(méi)有關(guān)系,這里不再展開(kāi)說(shuō)明了,這幾個(gè)概念都與消息轉(zhuǎn)發(fā)關(guān)系密切,感興趣大家可以看源碼。

runtime經(jīng)典圖分析


相信了解runtime的朋友對(duì)這張圖不陌生,我們?cè)谶@里再看看這張圖說(shuō)明了什么:

1.橫向看:實(shí)例是對(duì)象,類也是對(duì)象(類對(duì)象),meta類也是對(duì)象(原類對(duì)象)

這是很重要的一點(diǎn),希望大家理解,我們這里忽略上下結(jié)構(gòu),先看左右結(jié)構(gòu),從左到右的指向就是之前介紹的runtime源碼中objc_class結(jié)構(gòu)里isa的指向,Instance指的是我們創(chuàng)建的對(duì)象,Subclass(class)就是創(chuàng)建該對(duì)象的那個(gè)類,注意:創(chuàng)建對(duì)象的類本身也是對(duì)象,稱為類對(duì)象,類對(duì)象中存放的是描述實(shí)例相關(guān)的信息,例如實(shí)例的成員變量,實(shí)例方法

類對(duì)象里的isa指針指向Subclass(meta),Subclass(meta)也是一個(gè)對(duì)象,是原類對(duì)象,原類對(duì)象中存放的是描述類相關(guān)的信息,例如類方法,在這一過(guò)程中,isa的兩次指向很像很像,大家注意理解。

類本身作為一個(gè)對(duì)象這件事情其實(shí)還是值得我們花時(shí)間來(lái)想想的,我當(dāng)時(shí)是在想NSTimer自釋放問(wèn)題的時(shí)候想到類對(duì)象的,從而才發(fā)現(xiàn)了本文討論的問(wèn)題。

由于Subclass(meta)在橫向上已經(jīng)沒(méi)有可以指向的對(duì)象了,所以他們的isa指針統(tǒng)一指向縱向(繼承關(guān)系)上的根meta class。而根meta class的isa則指向自己,我們后面會(huì)在代碼中把這些結(jié)論性的東西驗(yàn)證了。

2.縱向看:

superclass指針很容易理解,就是按照繼承關(guān)系向上指的,一直到繼承鏈的最上方,值得說(shuō)的是Root class(class)的superclass指向是nil,Root class(meta)的superclass指向它的Root class (class),這個(gè)注意一下。

從代碼上理解上面的圖

這里介紹幾個(gè)runtime中的方法,還是看runtime源碼:

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

object_getClass()就是順著isa的指向鏈找到對(duì)應(yīng)的類,我們一會(huì)要驗(yàn)證這個(gè)isa的指向鏈?zhǔn)欠衽c上面圖中是一致的,就是用這個(gè)方法。

與之相關(guān)還有一個(gè)方法:object_setClass(),我們可以用該方法,簡(jiǎn)單的來(lái)看一下runtime的強(qiáng)大,它可以動(dòng)態(tài)改變類。首先要知道我們?cè)贜SLog的時(shí)候用的%@打印對(duì)象的時(shí)候其實(shí)是調(diào)用該類的description方法,而且我們還知道,NSArray對(duì)象的description會(huì)把每個(gè)array里的元素都打印出來(lái),而NSObject對(duì)象的description就僅僅打印類名和指針,下面通過(guò)一小段代碼看看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),這里再?gòu)?qiáng)調(diào)一下:類方法是在meta class里的,類方法就是把自己返回,而實(shí)例方法中是返回實(shí)例isa的類,我們要驗(yàn)證這個(gè)isa的指向鏈的時(shí)候不能用這種方法,千萬(wàn)記住,為什么,一會(huì)說(shuō)明。

看代碼:

#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是一個(gè)繼承自NSObject的普通類,里面有個(gè)name屬性。

1.我們發(fā)現(xiàn)調(diào)用class方法的方式不能得到isa的指向鏈,但是第一次調(diào)用是正確的(class的輸出都是0x10ae0e178),為什么?原因就是上面貼出來(lái)的class源碼中,我們第一次調(diào)用的class是實(shí)例方法,會(huì)返回isa的類,但是第二次開(kāi)始調(diào)用的就是類方法,返回的是本身,所以還是0x10ae0e178,以后無(wú)論怎么調(diào)用都是執(zhí)行的類方法,返回的都是本身,所以,用class方法是得不到isa指向鏈的。

2.用object_getClass()驗(yàn)證了我們Class、Object結(jié)構(gòu)模型理論是對(duì)的,我們這里特意的打印了root meta class 的isa,發(fā)現(xiàn)果然指向是自己(0x10b66a198)。

3.從打印結(jié)果我們能看到,類也是對(duì)象,meta類也是對(duì)象,都占有一塊內(nèi)存,而且我們會(huì)發(fā)現(xiàn)類對(duì)象、meta類對(duì)象、root meta類對(duì)象的指針都是用9位16進(jìn)制數(shù)表示,而實(shí)例對(duì)象是用12位16進(jìn)制數(shù)表示(這里用的是64位模擬器),為什么這些類對(duì)象的指針位數(shù)少?因?yàn)樗鼈兇嬖谟诙紊希⒉辉跅;蛘叨焉希?a href="http://www.itdecent.cn/p/55180ade32d1" target="_blank">黑魔法那篇文章說(shuō)過(guò)段內(nèi)存的事情。也就是說(shuō)可以把這些類對(duì)象理解成單利,這是很重要的一點(diǎn),希望大家理解,這一點(diǎn)可以讓我們天馬行空的想很多,比如可不可以把網(wǎng)絡(luò)請(qǐng)求寫(xiě)在類對(duì)象里,嫩不能用類對(duì)象去解決自釋放的問(wèn)題,等等...這會(huì)是很有意思的思考。

我們理解了這個(gè)結(jié)構(gòu)模型之后,看看我遇到的問(wèn)題吧。

問(wèn)題來(lái)了

看代碼:

#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

問(wèn)題來(lái)了:
為什么[NSTimer class]:0x10ecdfe38與class:0x10ece02c0得到的指針不一樣?

就是說(shuō)為什么object_getClass(obj)與[OBJ class]返回的指針不同?

[NSTimer class]返回應(yīng)該是類對(duì)象,object_getClass(timer1)返回的也應(yīng)該是類對(duì)象,上面也說(shuō)過(guò),可以把類對(duì)象理解成單利,為什么指針不同?

如果用Person類做實(shí)驗(yàn)兩者返回就是相同的,如果用系統(tǒng)其它類做實(shí)驗(yàn)兩者返回還是不同的,它們本身之間就有矛盾,更重要的是,與我們剛剛理解的結(jié)構(gòu)模型也是矛盾的,如何用這個(gè)模型理論去解釋[NSTimer class]返回的這個(gè)指針?

感興趣的朋友可以不往下看,自己想想為什么,其實(shí)很簡(jiǎn)單,但是沒(méi)想到會(huì)是這樣的,我當(dāng)時(shí)就是這個(gè)感受。


面朝大海,春暖花開(kāi)

答案來(lái)了

答案非常簡(jiǎn)單,兩個(gè)字:類簇

看代碼:

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]);

代碼沒(méi)有變,只是我們這次不打印指針,打印對(duì)象的描述:

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)識(shí)沒(méi)有錯(cuò),之所以矛盾是因?yàn)镹STimer是個(gè)類簇,它返回的并不是NSTimer對(duì)象,而是__NSCFTimer對(duì)象!我沒(méi)有想到NSTimer也是類簇,我們熟悉的類簇是NSNumber,NSArray,NSDictionary、NSString...這說(shuō)明大多數(shù)的OC類都是類簇實(shí)現(xiàn)的(連NSTimer也不放過(guò)),也說(shuō)明為什么我們Person類是正常的,因?yàn)樗皇穷惔貙?shí)現(xiàn)的。

這里還是簡(jiǎn)單的說(shuō)說(shuō)類簇的概念吧:一個(gè)父類有好多子類,父類在返回自身對(duì)象的時(shí)候,向外界隱藏各種細(xì)節(jié),根據(jù)不同的需要返回的其實(shí)是不同的子類對(duì)象,這其實(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類,而是各個(gè)子類,__NSCFNumber、__NSCFBoolean。

這和我們上面問(wèn)題中的NSTimer很像,類方法
scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:并沒(méi)有返回NSTimer對(duì)象,而是返回了它的子類__NSCFTimer對(duì)象。

有人可能要問(wèn),如何證明__NSCFTimer就是NSTimer的子類,如何證明類簇是真的?其實(shí)很簡(jiǎn)單:

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

總結(jié)

我把這個(gè)問(wèn)題記錄下來(lái)就是希望這篇文章對(duì)恰好遇到這個(gè)問(wèn)題的朋友、有相同困惑的朋友一些幫助,如果是NSArray遇到了相同的問(wèn)題我可能立馬想到類簇,因?yàn)樗€有可變的對(duì)象,但是NSTimer...

我猜中了開(kāi)頭,可我猜不著這結(jié)局?!舷枷勺印洞笤捨饔巍?/p>

我猜到了結(jié)局,但猜不到原因,誰(shuí)讓iOS是封閉的系統(tǒng)呢?

歡迎大家和我交流溝通,文章中有任何錯(cuò)誤和漏洞,懇請(qǐng)指正,謝謝。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容