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

runtime講哪些東西?

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

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

  2. 實(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屬性。

  1. 我們發(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指向鏈的。

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

  3. 從打印結(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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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