OC對(duì)象(三)-- isa結(jié)構(gòu)分析

OC對(duì)象(一)-- alloc和init底層到底在干嘛
OC對(duì)象(二)-- 內(nèi)存對(duì)齊和calloc中的16字節(jié)對(duì)齊
OC對(duì)象(三)-- isa結(jié)構(gòu)分析

開(kāi)場(chǎng)白

本文主要講解isa結(jié)構(gòu)和isa的賦值過(guò)程

1割坠、isa

實(shí)例對(duì)象在內(nèi)存中首地址就是isa舌狗,其實(shí)就是用來(lái)表示對(duì)象的類(lèi)是誰(shuí)。

DZPerson *obj = [[DZPerson alloc] init];
obj.name = @"DZ";
NSLog(@"%@", obj.name);

通過(guò)lldb打印obj的內(nèi)存情況:

通過(guò)調(diào)試收叶,po一下obj首地址中的第一個(gè)值是DZPerson膝晾。說(shuō)明isa中存儲(chǔ)著實(shí)例對(duì)象對(duì)應(yīng)的歸宿類(lèi)赏迟。

2约素、isa結(jié)構(gòu)


通過(guò)objc源碼查看isa是一個(gè)union位域的形式,源碼如下:

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD) //ISA_BITFIELD存在
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

條件編譯命令中的ISA_BITFIELD是存在的贝攒,所以后面的代碼是會(huì)被編譯進(jìn)去的盗誊。接下來(lái)看看ISA_BITFIELD是如何定義的

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)
  • 這里使用條件編譯命令區(qū)分__arm64__代表iOS。__x86_64__代表MAC
  • ISA_MASK:掩碼隘弊,用于直接獲取isa中類(lèi)信息的哈踱。也就是通過(guò)掩碼可以直接得到shiftcls
  • nonpointer:表示是否對(duì)isa指針開(kāi)啟指針優(yōu)化
    • 0:isa就是一個(gè)指針
    • 1:isa不只是一個(gè)簡(jiǎn)單的指針,里面還包含了類(lèi)信息长捧,引用計(jì)數(shù)等等信息嚣鄙。通常自定義的類(lèi)實(shí)例對(duì)象都是這個(gè)類(lèi)型。
  • has_assoc:是否有關(guān)聯(lián)對(duì)象
  • has_cxx_dtor:是否有c++或Objc析構(gòu)器
  • shiftcls:存儲(chǔ)類(lèi)指針的值。注意iOS和Mac中占用的位數(shù)不一樣
  • magic:?于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒(méi)有初始化的空間
  • weakly_referenced:對(duì)象有沒(méi)有被若引用
  • deallocating:對(duì)象是否正在釋放中
  • has_sidetable_rc:是否有引用計(jì)數(shù)散列表
  • extra_rc:提供給引用計(jì)數(shù)使用的酵紫。

擴(kuò)展 - union位域

union位域可以節(jié)省內(nèi)存空間的開(kāi)辟舵变,舉個(gè)例子:
定義一個(gè)DZPeront類(lèi)咬崔,里面含有四個(gè)屬性矢棚,分別代表走路的四個(gè)方向

@interface DZPerson : NSObject
@property (assign, nonatomic) BOOL front;
@property (assign, nonatomic) BOOL back;
@property (assign, nonatomic) BOOL left;
@property (assign, nonatomic) BOOL right;
@end

這四個(gè)屬性都是BOOL類(lèi)型妓蛮,一個(gè)BOOL類(lèi)型占用1個(gè)字節(jié)侦香。因此需要內(nèi)存開(kāi)辟4字節(jié)的空間把敞。BOOL類(lèi)型其實(shí)就是兩種值YES or NO弥奸,對(duì)應(yīng)的二進(jìn)制是0000 0001 or 0000 0000,如圖:

如果把前面的幾位利用上奋早,就可以達(dá)到減少內(nèi)存占用盛霎。使用union位域就可以實(shí)現(xiàn)這個(gè)目的。

union {
    char bits;
    struct{
        char front :1;
        char back :1;
        char left :1;
        char right :1;
    };
} _walkDirection;

聯(lián)合體(union)中還包含了一個(gè)結(jié)構(gòu)體(struct)耽装,這個(gè)結(jié)構(gòu)體中的成員后面跟著的冒號(hào)和數(shù)字愤炸,數(shù)字就是代表占用的位數(shù)(語(yǔ)法要求,這個(gè)要死記硬背)掉奄,這個(gè)就是位域
需要注意:

  • 案例中的數(shù)字“1”规个,代表占用1位。1位可以表達(dá)的兩種情況:1和0姓建。如果想表達(dá)“0-7”的值诞仓,那么就需要定義為“3”

  • 位域中成員定義的位是按從低到高的方式存儲(chǔ)的

    nonuse nonuse nonuse nonuse right left back front
    0 0 0 0 1 1 1 1

上面的DZPerson可以修改成:

@interface DZPerson : NSObject{
    union {
        char bits;
        struct{
            char front :1;
            char back :1;
            char left :1;
            char right :1;
        };
    } _walkDirection;
}

- (void)setFront:(BOOL)isFront;
- (BOOL)isFront;

- (void)setBack:(BOOL)isBack;
- (BOOL)isBack;

@end

@implementation DZPerson

- (instancetype)init
{
    self = [super init];
    if (self) {
        _walkDirection.bits = 0b00000000;
    }
    return self;
}

- (void)setFront:(BOOL)isFront {
    if (isFront) {
        _walkDirection.bits |= kPersonWalkDirectionFrontMask;
    } else {
        _walkDirection.bits |= ~kPersonWalkDirectionFrontMask;
    }
}

- (BOOL)isFront {
    return _walkDirection.front;
}

- (void)setBack:(BOOL)isBack {
    _walkDirection.back = isBack;
}

- (BOOL)isBack {
    return _walkDirection.back;
}
@end

//調(diào)用
DZPerson *obj = [[DZPerson alloc] init];
[obj setFront:YES];
[obj setBack:YES];
NSLog(@"%@", obj.isFront ? @"YES": @"NO");

查看obj內(nèi)存情況,如圖:


此處內(nèi)存中十六進(jìn)制是0x3速兔,因?yàn)檎{(diào)用代碼設(shè)置的frontback墅拭,二進(jìn)制表示:0b11

3、isa的賦值過(guò)程

實(shí)例alloc有重要的三步驟(不清楚的可以翻看之前的博客)

  1. 計(jì)算需要分類(lèi)的空間大谢凉贰:size = cls->instanceSize(extraBytes);
  2. calloc分配內(nèi)存:obj = (id)calloc(1, size);
  3. 實(shí)例對(duì)象的初始化:obj->initInstanceIsa(cls, hasCxxDtor);

此處主要聊聊第三部做了什么:

static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ...//省略不關(guān)心代碼
    size = cls->instanceSize(extraBytes);
    ...//省略不關(guān)心代碼    
    obj = (id)calloc(1, size);
    ...//省略不關(guān)心代碼
    
    //核心入口,加星標(biāo)記???????????????? 
    obj->initInstanceIsa(cls, hasCxxDtor);
    //???????????????? 
    
    ...//省略不關(guān)心代碼
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

???

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

???

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {//傳入?yún)?shù)是true帜矾,不會(huì)走這個(gè)分支
        ......
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        //核心入口 ??????
        isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        isa = newisa;
    }
}

核心研究的地方就是對(duì)isa中的位進(jìn)行賦值,注意類(lèi)的相關(guān)信息賦值在newisa.shiftcls位中屑柔。

擴(kuò)展 - 獲取isa中的類(lèi)信息

runtime的api中提供了獲取對(duì)象類(lèi)方法object_getClass屡萤,接下來(lái)研究一下源碼的實(shí)現(xiàn):

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

???

inline Class 
objc_object::getIsa() 
{
    if (fastpath(!isTaggedPointer())) return ISA();
    ......
    //省略無(wú)關(guān)代碼
}

???

inline Class 
objc_object::ISA() 
{
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    //核心入口 ??????
    return (Class)(isa.bits & ISA_MASK);
#endif
}
  • 最后調(diào)用的是isa.bits & ISA_MASK,這一步獲取的就是isa中shiftcls的值(ISA_MASK上面有講過(guò)掸宛,是個(gè)宏死陆,在不同操作系統(tǒng)定義的值也不同)。
  • 得到的值進(jìn)行Class類(lèi)型強(qiáng)轉(zhuǎn)唧瘾,這也是為什么我們?cè)诳聪嚓P(guān)源碼中措译,isa都是定義成Class類(lèi)型原因。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饰序,一起剝皮案震驚了整個(gè)濱河市领虹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌求豫,老刑警劉巖塌衰,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诉稍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡最疆,警方通過(guò)查閱死者的電腦和手機(jī)杯巨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)努酸,“玉大人服爷,你說(shuō)我怎么就攤上這事』裾” “怎么了仍源?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)舔涎。 經(jīng)常有香客問(wèn)我笼踩,道長(zhǎng),這世上最難降的妖魔是什么终抽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任戳表,我火速辦了婚禮,結(jié)果婚禮上昼伴,老公的妹妹穿的比我還像新娘匾旭。我一直安慰自己,他們只是感情好圃郊,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布价涝。 她就那樣靜靜地躺著,像睡著了一般持舆。 火紅的嫁衣襯著肌膚如雪色瘩。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天逸寓,我揣著相機(jī)與錄音居兆,去河邊找鬼。 笑死竹伸,一個(gè)胖子當(dāng)著我的面吹牛泥栖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播勋篓,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吧享,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了譬嚣?” 一聲冷哼從身側(cè)響起钢颂,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拜银,沒(méi)想到半個(gè)月后殊鞭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體遭垛,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年钱豁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耻卡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疯汁。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡牲尺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出幌蚊,到底是詐尸還是另有隱情谤碳,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布溢豆,位于F島的核電站蜒简,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏漩仙。R本人自食惡果不足惜搓茬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望队他。 院中可真熱鬧卷仑,春花似錦、人聲如沸麸折。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)垢啼。三九已至窜锯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芭析,已是汗流浹背锚扎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留馁启,地道東北人驾孔。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像进统,于是被迫代替她去往敵國(guó)和親助币。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348