想要成爲一名
iOS開發高手
,免不了閱讀源碼。如下是筆者在OC源碼探索
中梳理的一個小系列——類與對象篇,歡迎你們閱讀指正,同時也但願對你們有所幫助。程序員
- OC源碼分析之對象的建立
- OC源碼分析之isa
- 未完待續...
isa
介紹isa
是什麼在 OC源碼分析之對象的建立 一文中,咱們知道alloc
底層會調用calloc
分配內存,接着就是initInstanceIsa(cls, hasCxxDtor)
,顧名思義是初始化對象的isa,其關鍵代碼以下數據結構
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
// 留意這裏的true
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
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;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
複製代碼
Tagged Pointer
聽說,爲了節省內存和提升執行效率,蘋果提出了Tagged Pointer
的概念。對於 64 位程序,引入Tagged Pointer
後,相關邏輯能減小一半的內存佔用,以及 3倍 的訪問速度提高,100倍 的建立、銷燬速度提高。架構
Tagged Pointer
首次應用於iPhone 5s
設備上,如今幾乎都應用Tagged Pointer
了。想了解更多關於Tagged Pointer
的內容可戳 深刻理解 Tagged Pointer。ide
isa_t
類型點擊objc_object::initIsa()
中的isa
,發現isa
是isa_t
類型函數
struct objc_object {
private:
isa_t isa;
... // 一些isa的公有、私有方法
};
複製代碼
而isa_t
其實是一個union
(即聯合體,也叫共用體)源碼分析
這裏先普及一下
struct
和union
的區別post
- 二者均可以包含多個不一樣類型的數據,如
int
、double
、Class
等。- 在
struct
中各成員有各自的內存空間,一個struct
變量的內存總長度大於等於各成員內存長度之和;而在union
中,各成員共享一段內存空間,一個union
變量的內存總長度等於各成員中內存最長的那個成員的內存長度。- 對
struct
中的成員進行賦值,不會影響其餘成員的值;對union
中的成員賦值時,每次只能給一個成員賦值,同時其它成員的值也就不存在了。
isa_t
包含了cls
和bits
兩個成員變量,其結構以下性能
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
的bits
成員變量這裏普及一下位域的概念優化
位域是一種數據結構,能夠把數據以位的形式緊湊的儲存,並容許程序員對此結構的位進行操做。ui
- 優勢:
- 節省儲存空間;
- 能夠很方便的訪問一個整數值的部份內容從而能夠簡化程序源代碼。
- 缺點:
- 其內存分配與內存對齊的實現方式依賴於具體的機器和系統,在不一樣的平臺可能有不一樣的結果,這致使了位段在本質上是不可移植的。
isa
的bits
成員變量類型是uintptr_t
,它實質上是個unsigned long
typedef unsigned long uintptr_t;
複製代碼
在64位
CPU架構下bits
長度爲64位,也就是8字節,其各個位的存儲就使用了位域,即ISA_BITFIELD
。
ISA_BITFIELD
接下來看一下ISA_BITFIELD
的源碼(因爲筆者是用macOS
項目研究OC底層源碼,因此這裏以x86_64
架構爲例)
# 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)
複製代碼
首先明確一點,在64位
CPU架構下isa指針
的長度也是8字節
,它能夠存儲足夠多的內容,蘋果爲了優化性能,存儲類地址只用了一部分位(x86_64
下是44位,arm64
下是33位),剩下的位用來存儲一些其它信息。
具體分析一下ISA_BITFIELD
位域各成員的表示意義:
nonpointer
:表示是否對 isa指針
開啓指針優化。
isa指針
,當訪問isa
指針時,直接返回其成員變量cls
isa 指針
內容不止是類地址,還包含了類的一些信息、對象的引用計數等。has_assoc
:是否有關聯對象。has_cxx_dtor
:該對象是否有C++或Objc的析構器。
shiftcls
:存儲類指針的值。開啓指針優化的狀況下,在 x86_64
架構有 44位 用來存儲類指針,arm64
架構中有 33位 。magic
:用於調試器判斷當前對象是真的對象,仍是一段沒有初始化的空間。weakly_referenced
:用於標識對象是否被指向或者曾經被指向一個ARC
的弱變量,沒有弱引用的對象釋放的更快。deallocating
:標識對象是否正在釋放內存。has_sidetable_rc
:對象的引用計數值是否有進位。extra_rc
:表示該對象的引用計數值。extra_rc
只是存儲了額外的引用計數,實際的引用計數公式:實際引用計數 = extra_rc + 1
。這裏佔了8位,因此理論上能夠存儲的最大引用計數是:2^8 - 1 + 1 = 256
(arm64
CPU架構下的extra_rc
佔19位,可存儲的最大引用計數爲2^19 - 1 + 1 = 524288
)。
has_sidetable_rc
的關聯:當對象的最大引用計數超過界限後,has_sidetable_rc
的值爲1,不然爲0isa
的cls
成員變量分析完isa
的位域,接下來就只剩下cls
,它是Class
類型,一樣上源碼
typedef struct objc_class *Class;
// 順便了解一下id的類型,顯然id是個指針變量,它的值只有一個isa變量
typedef struct objc_object *id;
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;
class_data_bits_t bits;
class_rw_t *data() {
return bits.data();
}
... // 一些方法
};
struct objc_object {
private:
isa_t isa;
... // 一些isa的公有、私有方法
};
複製代碼
從源碼得知,Class
其實是objc_class
結構體的指針變量,而objc_class
又繼承自objc_object
,所以Class
這個結構體指針變量的值內部有一個isa
成員變量(類型爲isa_t
),這個isa
成員變量在64位
CPU架構下是8字節,且排在objc_class
結構體的前8字節。
isa
的做用經過對isa
的位域說明,咱們知道shiftcls
存儲的是類指針的值。在x86_64
架構下,shiftcls
佔用44位,也就是第[3, 46]位。將 [3, 46]位 所有填充1,[0, 2]位 和 [47~63]位 都補0,獲得0x7ffffffffff8,也就是ISA_MASK
的值。故,isa & ISA_MASK
會獲得shiftcls
存儲的類指針的值。這也就是所謂MASK
的做用。
以下圖所示
下面用一個例子說明isa
的做用
此時經過lldb
命令調試
說明:
0x001d800100001129
是對象p
的isa
值,經過isa & ISA_MASK
運算獲得的0x0000000100001128
就是Person
類的地址- 證實【1】:經過
p/x Person.class
直接打印Person
類地址,顯然獲得的是0x0000000100001128
,如此【1】證實成立!
結論:isa
將對象和類關聯起來,起到了中間橋樑的做用。
思考:若是不用
ISA_MASK
,那麼如何證實isa
的這個做用呢?——答案將在文末補充。
isa
的初始化補充最後補充一下isa
的初始化。還記得初始化isa
的入口嗎?是initIsa(cls, true, hasCxxDtor);
,此時nonpointer
的值是true
,再看SUPPORT_INDEXED_ISA
的定義
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
複製代碼
在x86_64
下,SUPPORT_INDEXED_ISA
是0,因此isa
的初始化最終會來到
isa_t newisa(0);
// 使用ISA_MAGIC_VALUE(0x001d800000000001ULL)賦值給bits
// nonpointer爲1,magic爲1d,其餘變量爲零
newisa.bits = ISA_MAGIC_VALUE;
// hasCxxDtor是從類的isa中取出的
newisa.has_cxx_dtor = hasCxxDtor;
// 將cls右移3位後賦值給shiftcls
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
複製代碼
礙於篇幅,這裏不繼續深刻
hasCxxDtor
。
isa
指向圖經過以上的源碼分析,咱們認識到對象的isa指針
指向了對象所屬的類。而類自己也有一個isa
指針,它指向的又是什麼呢?
蘋果官方有個isa
指向圖,即
從圖可知,類的isa指針
指向的是類的元類。如今咱們來驗證一下吧。
建立Teacher
類、Person
類,其中Person
類繼承於NSObject
,Teacher
類繼承於Person
類。
對比
isa指向圖
,對號入座後就是,Teacher
類至關於Subclass
,Person
類至關於Superclass
,NSObject
至關於Root class
。
teacher
對象的類(結果是Teacher
類,地址爲0x0000000100001230
)(lldb) x/4gx teacher
0x100f59400: 0x001d800100001231 0x0000000000000000
0x100f59410: 0x636f72504b575b2d 0x70756f7247737365
(lldb) p/x 0x001d800100001231 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $3 = 0x0000000100001230 // 對象teacher的類地址
(lldb) po $3
Teacher // 對象teacher的類
複製代碼
Teacher
類的元類(結果是Teacher元類
,地址爲0x0000000100001208
)(lldb) x/4gx Teacher.class
0x100001230: 0x001d800100001209 0x00000001000011e0
0x100001240: 0x0000000100f61150 0x0000000100000003
(lldb) p/x 0x001d800100001209 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $5 = 0x0000000100001208 // Teacher類的元類地址
(lldb) po $5
Teacher // Teacher類的元類
複製代碼
Teacher類 和 Teacher元類 地址不同
person
對象的類(結果是Person
類,地址爲0x00000001000011e0
),以及類的元類(結果是Person元類
,地址爲0x00000001000011b8
)(lldb) x/4gx person
0x100f60a30: 0x001d8001000011e1 0x0000000000000000
0x100f60a40: 0x0000000000000002 0x00007fff9b855588
(lldb) p/x 0x001d8001000011e1 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $8 = 0x00000001000011e0 // 對象person的類地址
(lldb) po $8
Person // 對象person的類
(lldb) x/4gx Person.class
0x1000011e0: 0x001d8001000011b9 0x0000000100b38140
0x1000011f0: 0x0000000100f61030 0x0000000100000003
(lldb) p/x 0x001d8001000011b9 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $10 = 0x00000001000011b8 // Person類的元類地址
(lldb) po $10
Person // Person類的元類
複製代碼
object
對象的類(結果是NSObject
類,地址爲0x0000000100b38140
),以及類的元類(結果是NSObject元類
,地址爲0x0000000100b380f0
)(lldb) x/4gx object
0x100f5cc50: 0x001d800100b38141 0x0000000000000000
0x100f5cc60: 0x70736e494b575b2d 0x574b57726f746365
(lldb) p/x 0x001d800100b38141 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $12 = 0x0000000100b38140 // 對象object的類地址
(lldb) po $12
NSObject // 對象object的類
(lldb) x/4gx NSObject.class
0x100b38140: 0x001d800100b380f1 0x0000000000000000
0x100b38150: 0x0000000101913060 0x0000000200000003
(lldb) p/x 0x001d800100b380f1 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $14 = 0x0000000100b380f0 // NSObject類的元類地址
(lldb) po $14
NSObject // NSObject類的元類
複製代碼
Teacher元類
的元類,Person元類
的元類,以及NSObject元類
的元類(lldb) x/4gx 0x0000000100001208 // Teacher元類
0x100001208: 0x001d800100b380f1 0x00000001000011b8
0x100001218: 0x000000010186f950 0x0000000400000007
(lldb) p/x 0x001d800100b380f1 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $16 = 0x0000000100b380f0 // NSObject元類
(lldb) po $16
NSObject // NSObject元類
(lldb) x/4gx 0x00000001000011b8 // Person元類
0x1000011b8: 0x001d800100b380f1 0x0000000100b380f0
0x1000011c8: 0x0000000101905a50 0x0000000300000007
(lldb) p/x 0x001d800100b380f1 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $17 = 0x0000000100b380f0 // NSObject元類
(lldb) po $17
NSObject // NSObject元類
(lldb) x/4gx 0x0000000100b380f0 // NSObject元類
0x100b380f0: 0x001d800100b380f1 0x0000000100b38140
0x100b38100: 0x0000000101903820 0x0000000500000007
(lldb) p/x 0x001d800100b380f1 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $18 = 0x0000000100b380f0 // NSObject元類
(lldb) po $18
NSObject // NSObject元類
複製代碼
isa
指向結論基於【2.2】的驗證過程,能夠得出結論:
isa指針
指向 對象的所屬類(如person
對象的isa
指向Person
類)isa指針
指向 類的元類(如Person
類的isa
指向Person元類
)isa指針
指向 根元類(如Person元類
的isa
指向NSObject元類
)
NSObject類
的元類是根元類
NSObject元類
的isa指針
指向自身(是個圓圈)思考:若是Person類繼承的是NSProxy,相關isa指向是怎樣的呢?感興趣的能夠去試試。
類的繼承關係證實過程:(以 -> 表示 繼承自)
(lldb) p class_getSuperclass(Teacher.class)
(Class) $19 = Person // Teacher類 -> Person類
(lldb) p class_getSuperclass(Person.class)
(Class) $20 = NSObject // Person類 -> NSObject類
(lldb) p class_getSuperclass(NSObject.class)
(Class) $21 = nil // NSObject類 -> nil
複製代碼
元類的繼承關係證實過程:(以 -> 表示 繼承自)
// 0x0000000100001208 是 Teacher元類
(lldb) p/x class_getSuperclass((Class)0x0000000100001208)
(Class) $17 = 0x00000001000011b8 // Person元類
(lldb) po $17
Person // Teacher元類 -> Person元類
// 0x00000001000011b8 是 Person元類
(lldb) p/x class_getSuperclass((Class)0x00000001000011b8)
(Class) $22 = 0x0000000100b380f0 // NSObject元類(根元類)
(lldb) po $22
NSObject // Person元類 -> 根元類
// 0x0000000100b380f0 是 根元類
(lldb) p/x class_getSuperclass((Class)0x0000000100b380f0)
(Class) $23 = 0x0000000100b38140 NSObject // NSObject類(根類)
(lldb) po $23
NSObject // 根元類 -> 根類
複製代碼
根元類繼承自根類(NSObject元類 -> NSObject類),根類繼承自nil(NSObject類 -> nil)
isa
指向圖塗鴉版把上面的例子塗在isa
指向圖上,就獲得了下圖
isa
是isa_t
結構,採用 聯合體+位域 的搭配來設計:在不一樣的位上顯示不一樣的內容,以此來節省儲存空間,進而優化內存。isa
包含了cls
和bits
兩個成員變量,這兩個成員變量在64位
CPU架構下的長度都是8字節,因此isa
在64位
CPU架構下的長度也是8字節。isa
的位域上存儲了一些對象與類的信息,並將對象與類關聯起來,起到中間橋樑的做用。isa
指向圖相關結論:
isa指針
指向 對象的所屬類(如person
對象的isa
指向Person
類)isa指針
指向 類的元類(如Person
類的isa
指向Person元類
)isa指針
指向 根元類(如Person元類
的isa
指向NSObject元類
)
根元類
的isa指針
指向自身(是個圓圈)Teacher元類
繼承自 Person元類
)
根元類
繼承自 根類
根類
繼承自 nil
isa
的做用的證實2Q:若是不用ISA_MASK
,那麼如何證實isa
關聯了對象和類的做用呢?
A:具體思路是,shiftcls
在x86_64
架構下長度是44位,存儲在isa
的 [3, 46]位上,因此能夠經過將isa
的 [0, 2]位、[47, 63]位清零,一樣能獲得shiftcls
的值,進而肯定類。
如圖所示,通過對isa
的一番運算,成功獲得與Person
類相同的地址。
NSProxy
的isa
指向Q:若是Person類繼承的是NSProxy,相關isa指向是怎樣的呢?
A:跟NSObject
同樣,二者都是根類
。