看透isa

前言

在寫這篇博客之前齐婴,我在想要從哪里切入单匣,才能讓iOS開發(fā)者能更通俗的理解 isa蚁阳。思來想去铃绒,我覺得還是從我們最熟悉的“對象”入手吧。

在Foundation層韵吨,創(chuàng)建對象的代碼是這樣的

Person *p = [[Person alloc] init];

那么你有沒有想過這樣一個問題匿垄?我們自定義了一個Person類移宅,沒有任何屬性和方法,為什么我們可以調(diào)用 allocinit 呢 椿疗?或許你可以脫口而出漏峰,因為Person類繼承自NSObject,NSObject里有默認的實現(xiàn)

+ (id)alloc {
    return _objc_rootAlloc(self);
}

那為什么繼承自NSOject的類就可以調(diào)用NSObject的方法呢届榄?是不是這中間兩者通過某些線索進行了關聯(lián)呢浅乔?帶著這個疑問我們往下看。

初識 isa

對象的本質(zhì)是 結構體铝条,這很好理解靖苇,因為OC 是 C 與 C++ 的超集。一個對象可以有多種不同數(shù)據(jù)類型的屬性班缰,那可以容納不同數(shù)據(jù)類型的復雜結構贤壁,當然是結構體了。我們通過查看蘋果的源碼也可以佐證這一說法埠忘。

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

我們看到脾拆,對象就是這樣一個結構體,且被typedef為 id 莹妒。在Foundation層 id 就表示一個對象 名船。

同時我們注意到在對象結構體內(nèi),有一個 Class 類型的 isa 變量旨怠,看變量類型這是一個類渠驼。對象內(nèi)有一個類 ?這聽起來有些奇怪鉴腻;對象內(nèi)有一個指向該對象類型的指針 迷扇?這似乎還蠻符合我們以往的認知:在面向?qū)ο缶幊讨校瑢ο笫怯深悇?chuàng)建的拘哨,對象可以通過 isa 變量找到自己所屬的類谋梭。

那為什么對象需要知道自己的類呢?這主要是因為對象的信息是存儲在該對象所屬的類中的倦青。

這也很容易理解瓮床,一個類可以有多個對象,如果每個對象的信息都存儲在各自的本身产镐,那隨著對象的不斷創(chuàng)建隘庄,對于內(nèi)存來說是災難級的。

既然對象的 isa 指針指向了類癣亚,那不妨也看看類的結構:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    Class _Nullable super_class                             
    .......
    .......
}

類里面有個 super_class丑掺,指向了類的父類; 同時類也有一個 isa 指針述雾,那類的 isa 指針指向了哪里呢街州?

對象是按照 所定義的各個屬性和方法“生產(chǎn)”的兼丰, 作為對象的模板,也可看成是對象唆缴。正如工廠里面的模子也是要專門制作模子的機器生產(chǎn)鳍征。元類 (meta class) 就是設計、管理類(class)的模板面徽。對象是 的實例艳丛,類是 元類 的實例。

所以類的 isa 指針指向了 元類趟紊。

按照這個規(guī)則氮双,那 元類 也是對象,元類對象中也有 isa霎匈,那么元類的 isa 又指向哪里呢戴差?總不能指向元元類吧……這樣是無窮無盡的。

Objective-C語言的設計者已經(jīng)考慮到了這個問題唧躲,所有元類的 isa 都指向 根元類(meta Root Class)造挽。關于實例對象、類弄痹、元類之間的關系,蘋果官方給了一張圖嵌器,非常清晰的表明了三者的關系肛真。

isa流程圖

實線是 super_class 指針,虛線是 isa 指針爽航。

    1. Root class(class) 通常是 NSObject蚓让,NSObject 是沒有超類的,所以 Root class(class)的 superclass 指向 nil讥珍。
    1. 每個 Class 都有一個 isa 指針指向唯一的 Meta class
    1. Root class(meta)的 superclass 指向 Root class(class)历极,也就是 NSObject,形成一個回路衷佃。
    1. 每個 Meta class 的 isa 指針都指向 Root class(meta)趟卸。

一個對象 可以通過 isa 找到類,根據(jù)類的 isasuper_class 找到 元類 與 父類 氏义,進而直到 根元類 和 根類 锄列,所以 對于最開始的例子 Person *p = [[Person alloc] init]; Person可以調(diào)用NSObject的方法,在這中間 isa 起到至關重要的作用惯悠。

小結:

Object-C 是基于類的對象系統(tǒng)邻邮。每一個對象都是一些類的實例;這個對象的 isa 指針指向它所屬的類克婶。

  • 該類描述這個 對象的數(shù)據(jù)信息 :內(nèi)存分配大小(allocation size)和實例變量的類型(ivar types )與布局(layout)筒严;
  • 也描述了 對象的行為 :它能夠響應的選擇器(selectors)和它實現(xiàn)的實例方法(instance methods)丹泉。

每個 Object-C 類也是一個對象,它的 isa 指針指向元類鸭蛙,元類是關于類對象的描述摹恨,就像類是普通實例對象的描述一樣。

一個元類是根元類的實例规惰;根元類是它自身的實例睬塌。

isa 指針鏈以一個環(huán)結束:實例指向類-指向元類-指向根元類-到自身。元類的 isa 指針并不重要歇万,因為在現(xiàn)實世界中揩晴,沒人會向元類對象發(fā)送消息。

總之贪磺, isa 很棒~ 很重要~

isa的優(yōu)化

隨著Apple公司的發(fā)展硫兰,iPhone 不斷更新迭代,技術不斷提升寒锚,底層源碼也是在不斷優(yōu)化的劫映。64位架構CPU問世,Apple更新優(yōu)化了許多地方刹前,其中就包括 isa 的結構泳赋。

/// Represents an instance of a class.
struct objc_object {

private:
    isa_t isa;

     ..........太多  以下省略
     ..........
}
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

在 objc2.0 中,所有的對象都會包含一個 isa_t 類型的結構體喇喉。同時祖今,因為 objc_class 繼承自 objc_object,所以所有的類也包含這樣一個 isa拣技。在優(yōu)化之前千诬,isa 只是一個指向類或元類的指針,而優(yōu)化之后膏斤,采取了聯(lián)合體結構徐绑,同樣是占用8字節(jié)空間,但存儲了更多的內(nèi)容莫辨。

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

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

其中 ISA_BITFIELD 為宏傲茄,定義在 isa.h 中,這樣做的目的是為了區(qū)分不同架構

isa_t中的struct

深入 isa

我們以 arm64 架構為例衔掸,則 isa_t可以表示成如下所示的代碼

(以下內(nèi)容探討如不特殊說明烫幕,默認均是以 arm64 架構為例)

#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL

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

    Class cls;
    uintptr_t bits;
    
    struct {
        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
     }
};

isa_t 是一個聯(lián)合體,這里所占空間為 8字節(jié)敞映,共64位 较曼,內(nèi)存布局從低位到高位情況如下圖

isa_t內(nèi)存布局情況

解釋一下各存儲內(nèi)容的含義:

  • nonpointer(存儲在第0字節(jié)) : 是否為優(yōu)化isa標志。0代表是優(yōu)化前的isa振愿,一個純指向類或元類的指針捷犹;1表示優(yōu)化后的isa弛饭,不止是一個指針,isa中包含類信息萍歉、對象的引用計數(shù)等÷滤蹋現(xiàn)在基本上都是優(yōu)化后的isa。

  • has_assoc (存儲在第1個字節(jié)): 關聯(lián)對象標志位枪孩。對象含有或者曾經(jīng)含有關聯(lián)引用憔晒,0表示沒有,1表示有蔑舞,沒有關聯(lián)引用的可以更快地釋放內(nèi)存(dealloc的底層代碼有體現(xiàn))拒担。

  • has_cxx_dtor(存儲在第2個字節(jié)): 析構函數(shù)標志位,如果有析構函數(shù)攻询,則需進行析構邏輯从撼,如果沒有,則可以更快速地釋放對象(dealloc的底層代碼有體現(xiàn))钧栖。

  • shiftcls :(存儲在第3-35字節(jié))存儲類的指針低零,其實就是優(yōu)化之前 isa 指向的內(nèi)容。在arm64架構中有33位用來存儲類指針拯杠。x86_64架構有44位掏婶。

  • magic(存儲在第36-41字節(jié)):判斷對象是否初始化完成, 是調(diào)試器判斷當前對象是真的對象還是沒有初始化的空間潭陪。

  • weakly_referenced(存儲在第42字節(jié)):對象被指向或者曾經(jīng)指向一個 ARC 的弱變量气堕,沒有弱引用的對象可以更快釋放(dealloc的底層代碼有體現(xiàn))。

  • deallocating(存儲在第43字節(jié)):標志對象是否正在釋放內(nèi)存畔咧。

  • has_sidetable_rc(存儲在第44字節(jié)):判斷該對象的引用計數(shù)是否過大,如果過大則需要其他散列表來進行存儲揖膜。

  • extra_rc(存儲在第45-63字節(jié)誓沸。):存放該對象的引用計數(shù)值減1后的結果。對象的引用計數(shù)超過 1壹粟,會存在這個里面拜隧,如果引用計數(shù)為 10,extra_rc 的值就為 9趁仙。

如上洪添,優(yōu)化之后的 isa,保留了優(yōu)化之前類的指針(shiftcls)雀费,所以依然可以通過isa找到對應的類干奢,在類中通過super_class找到父類,這對于 isa 的指向圖的部分是一樣子盏袄。同時還包含了更多其他的內(nèi)容忿峻,這個設計和 taggedpointer有些類似薄啥,把內(nèi)存用到極致。

接下來我們做一些有趣的事情:

定義一個繼承自 NSObject 的類 Person逛尚,不添加任何屬性與方法等垄惧,保證它是剛剛創(chuàng)建出來的樣子。

Person *p = [[Person alloc] init];

以16進制格式化打印4段內(nèi)存情況

(lldb) x/4gx p
0x10201f950: 0x001d8001000024dd 0x0000000000000000
0x10201f960: 0x0000000000000000 0x0000000000000000
(lldb) 

因為Person繼承自NSObject绰寞,默認有一個 isa到逊,所以 0x001d8001000024dd 就是 isa_t 結構 ,我們將這個值
右移3位滤钱,左移31位觉壶,再右移28位,看看得到什么菩暗?

(lldb) x/4gx p
0x10201f950: 0x001d8001000024dd 0x0000000000000000
0x10201f960: 0x0000000000000000 0x0000000000000000
(lldb) po 0x001d8001000024dd >> 3
1037939513492635

(lldb) po 1037939513492635 << 30
562951189692416

(lldb) po 562951189692416 >> 27
Person

(lldb) 

最終結果顯示是拿到了類信息掰曾,我們來畫圖分析一下這個過程,用藍色表示內(nèi)存中被保留的值停团,灰色表示內(nèi)存中被抹除的值

    1. 起始時旷坦,完整內(nèi)存的值均保留
起始時isa_t內(nèi)存占滿.png
    1. 右移3位

內(nèi)存整體右移3位,那么高3位將空缺佑稠,低3位被移出isa_t內(nèi)存邊界(用透明度表示)秒梅,所以相當于抹除。

右移3位

我們只關注isa_t結構內(nèi)的內(nèi)存分布舌胶,不考慮邊界內(nèi)存的影響捆蜀,簡化繪圖為:

  • 3.左移31位

低31位被抹除

左移31位
  • 4.右移28位
右移28位

最終內(nèi)存中被保留的內(nèi)容 僅剩第3到第35字節(jié)的,對應前面所講的 isa_t 內(nèi)存布局情況幔嫂,剛好是 shiftcls 的數(shù)據(jù)信息辆它。所以我們上面的操作可以取到Person類信息。

我們再看一下apple的開發(fā)人員是怎么取類的信息的呢履恩?

inline Class 
objc_object::ISA() 
{
 
    return (Class)(isa.bits & ISA_MASK);
}

通過 isa中的 bits & ISA_MASK

看看 ISA_MASK 是什么锰茉?

#   define ISA_MASK        0x0000000ffffffff8ULL

將它轉換成2進制

ISA_MASK的二進制形式

從低位3開始到35位為1,其他位均為0切心。所以 & ISA_MASK 就相當于保留第3-35位數(shù)據(jù)飒筑,抹除其他位數(shù)據(jù)。依然是取 shiftcls 绽昏!

isa 的初始化

了解了isa的結構协屡,我們來看一下isa的初始化(去除一些宏定義,斷言以及條件判斷等全谤,我們直接將代碼減少到它執(zhí)行的代碼)

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
        isa_t newisa(0);

        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;

        isa = newisa;
    }
}

  1. 首先對整個bits進行賦值肤晓,傳入 ISA_MAGIC_VALUE ,在arm64架構下,該值為
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL

將該值轉換為2進制

ISA_MAGIC_VALUE轉換為2進制

對應 isa_t 中內(nèi)存布局的位置材原,可以看出對 bits 賦值 就是對 nonpinter 和 magic 賦值的過程沸久。

  1. 其次對has_cxx_dtor賦值。

  2. 最后對shifcls賦值

newisa.shiftcls = (uintptr_t)cls >> 3;

這里 余蟹,對當前傳入類進行右移3位的原因是卷胯,將cls指針后三位清除以減小內(nèi)存消耗,因為指針是要按照8字節(jié)對齊的威酒,實際后三位是沒有意義的窑睁。這和 isa_t 中的內(nèi)存布局沒有關系,因為類可不是按照isa_t進行內(nèi)存布局的葵孤。

至此isa的賦值過程就完成了担钮。

總結

對于 isa ,我們了解了底層原理,對其作用以及相關操作尤仍,我們會更加清晰箫津。當然,在這里我們也要學習Apple的設計模式宰啦,試著站在開發(fā)人員的角度考慮它的設計思想苏遥。

然后你一定要熟記 isasuper_class 的指向流程,這真的很重要赡模。

最后田炭,希望在此時或者以后的某一天,你可以大膽的對它說:isa漓柑,我看透你了教硫!

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市辆布,隨后出現(xiàn)的幾起案子瞬矩,更是在濱河造成了極大的恐慌,老刑警劉巖锋玲,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丧鸯,死亡現(xiàn)場離奇詭異,居然都是意外死亡嫩絮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門围肥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剿干,“玉大人,你說我怎么就攤上這事穆刻≈枚” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵氢伟,是天一觀的道長榜轿。 經(jīng)常有香客問我幽歼,道長,這世上最難降的妖魔是什么谬盐? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任甸私,我火速辦了婚禮,結果婚禮上飞傀,老公的妹妹穿的比我還像新娘皇型。我一直安慰自己,他們只是感情好砸烦,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布弃鸦。 她就那樣靜靜地躺著,像睡著了一般幢痘。 火紅的嫁衣襯著肌膚如雪唬格。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天颜说,我揣著相機與錄音购岗,去河邊找鬼。 笑死脑沿,一個胖子當著我的面吹牛藕畔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庄拇,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼注服,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了措近?” 一聲冷哼從身側響起溶弟,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瞭郑,沒想到半個月后辜御,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡屈张,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年赡茸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铁坎。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡晾腔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出场绿,到底是詐尸還是另有隱情剖效,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站璧尸,受9級特大地震影響咒林,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜爷光,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一垫竞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瞎颗,春花似錦件甥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至倦逐,卻和暖如春譬正,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背檬姥。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工曾我, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人健民。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓抒巢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親秉犹。 傳聞我的和親對象是個殘疾皇子蛉谜,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359