Runtime源碼剖析---圖解對象别凹、類與isa
源碼面前,了無秘密
- 在
iOS
開發(fā)的過程中洽糟,對象炉菲、類應該是我們接觸最的一個部分,本篇文章就以對象為主題坤溃,分一下對象和類在底層是如何實現(xiàn)的拍霜,讓你更深入了解iOS
開發(fā)。 - 從這篇博客開始我們就會進行Runtime源碼分析薪介,所以你需要準備一份最新的源代碼祠饺,源碼建議從Apple官方獲取
- 本篇博客所用的是750.1版本的objc4源碼(目前最新版)
對象
objc_object定義
- 在
OC
中每一個對象都是一個結(jié)構(gòu)體,結(jié)構(gòu)體中都包含一個isa
的成員變量汁政,其位于成員變量的第一位
如何在源碼中找到它道偷?
- 我們先在源碼中找到objc_object在哪,于是你打開全局搜索记劈,找到了這么一段
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// 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;
#endif
- 于是你認為它里面就一個
Class _Nonnull isa OBJC_ISA_AVAILABILITY
; - 然而勺鸦,請注意最上面的
#if !OBJC_TYPES_DEFINED
,點進去會發(fā)現(xiàn)該宏是1目木,說明根本不會走這個方法换途。 - 然而真正的定義是在objc-private文件里
struct objc_object {
private:
isa_t isa;
public:
//此處省略方法
};
- 我們不需要關(guān)心它的方法,我們來看看它的成員變量
isa_t
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_t
的作用就是用來存儲類的信息 - 關(guān)于
isa_t
這個結(jié)構(gòu)我們在下面會詳細剖析 - 我們把視線放在
Class cls
這個變量,這個到底是什么呢军拟?- 它其實就我們口中的類剃执,下面我們來仔細看看類的內(nèi)部實現(xiàn)
類
objc_class定義
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
//省略方法
}
成員變量
- 第一個變量
superclass
:指向他的父類 - 第二個變量
cache
:這里面存儲的方法緩存,這個知識點我會在下一篇文章中仔細剖析 - 第三個變量
bits
:存儲對象的方法吻谋、屬性忠蝗、協(xié)議等信息现横,這個知識點我會在下一篇文章中仔細剖析
繼承關(guān)系
-
從繼承關(guān)系我們就會發(fā)現(xiàn)漓拾,原來
objc_class
是繼承于objc_object
,- 那就是說類其實也是一個對象
- 還說明類實例化后也會包含
isa
這樣一個成員 - 用一張圖來表示
-
這個時候就會有一個疑問?
- 既然類繼承對象戒祠,它也有一個
isa
骇两,前面我們說了這個成員的作用是記錄類的信息的,那么我們類也擁有這個成員姜盈,那它也應該來記錄一些信息低千,那它記得的是什么信息呢?這個時候我們需要引進一個概念元類
注意??:
- 學習過程中馏颂,會發(fā)現(xiàn)很多人將
isa
稱之為isa
指針,的確在32位機時代它就是一個指針示血,但是在現(xiàn)在64位機時代,它是一個結(jié)構(gòu)體救拉,同時他也包含了指針的作用难审。具體為什么,我在后面會為大家詳細解釋 - 但是為方便講述亿絮,下面也開始使用isa指向xxx這種說法
- 既然類繼承對象戒祠,它也有一個
元類
元類的定義:元類是
Class
對象的類告喊,類的isa
會指向其元類根類的定義:根類是所有對象的父類(除了特殊情況),它沒有父類派昧,一般情況下就是指NSObject
-
為什么會定義元類這個類黔姜?
方法的調(diào)用機制:
因為在
Objective-C
中,對象的方法并沒有存儲于對象的結(jié)構(gòu)體中(如果每一個對象都保存了自己能執(zhí)行的方法蒂萎,那么對內(nèi)存的占用有極大的影響)秆吵。當實例方法被調(diào)用時,它要通過自己持有的
isa
來查找對應的類五慈,然后在這里的class_data_bits_t
結(jié)構(gòu)體中查找對應方法的實現(xiàn)巡揍。同時,每一個objc_class
也有一個指向自己的父類的指針super_class
用來查找繼承的方法烈涮。
- 既然類中存儲的是實例方法适揉,每個對象需要調(diào)用實例方法都來類里尋找即可,那么如果一個類需要調(diào)用類方法的時候聪轿,我們是如何查找并調(diào)用的呢爷肝?
- 這個時候就需要引入元類來保證無論是類還是對象都能通過相同的機制查找方法的實現(xiàn)。
-
引入元類這個概念后,這樣就達到了使類方法和實例方法的調(diào)用機制相同的目的:
- 實例方法調(diào)用時灯抛,通過對象的
isa
在類中獲取方法的實現(xiàn) - 類方法調(diào)用時金赦,通過類的
isa
在元類中獲取方法的實現(xiàn)
- 實例方法調(diào)用時灯抛,通過對象的
下面這張圖介紹了對象、類與元類之間的關(guān)系
- 注意??:
-
Root class
根類对嚼,它是繼承關(guān)系的頂點夹抗,它不繼承于任何類 -
Root meta class
根元類,它是isa
指向的頂點纵竖,其isa
直接指向自己,它繼承于根類
-
isa_t結(jié)構(gòu)剖析
結(jié)構(gòu)分析
- 我們先再來看一遍他的結(jié)構(gòu)
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
};
//struct中的結(jié)構(gòu)
# define ISA_BITFIELD
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8
注意??:這是在
__x86_64__
上的實現(xiàn)漠烧,對于 iPhone5s 等架構(gòu)為__arm64__
的設備上,具體結(jié)構(gòu)體的實現(xiàn)和位數(shù)可能有些差別靡砌,不過這些字段都是存在的已脓,由于源碼是在Mac OS運行的,所以我們就以__x86_64__
為例進行講解
-
isa_t
是一個union
的結(jié)構(gòu)對象通殃,union
類似于C++
結(jié)構(gòu)體度液,其內(nèi)部可以定義成員變量和函數(shù)。在isa_t
中定義了cls
画舌、bits
堕担、struct
三部分。聯(lián)合體的大小取決的最大的那個成員變量曲聂,最大就是struct
結(jié)構(gòu)體霹购,它占有64位,所以union
的大小就是64位
cls對象
- 在前面我已經(jīng)講過了它代表的是類句葵,如果有忘記的可以再回去看看厕鹃。
bits對象
- 它其實是一個
unsigned long
類型 - 它是用來獲取類指針,具體怎么操作下面我會詳解
struct
-
下面對
isa_t
中的結(jié)構(gòu)體進行了位域聲明乍丈,地址從nonpointer
起到extra_rc
結(jié)束剂碴,從低到高進行排列。位域也是對結(jié)構(gòu)體內(nèi)存布局進行了一個聲明轻专,通過下面的結(jié)構(gòu)體成員變量可以直接操作某個地址忆矛。位域總共占8字節(jié),所有的位域加在一起正好是64位请垛。小提示:
union
中bits
可以操作整個內(nèi)存區(qū)催训,而位域只能操作對應的位。 下面我們用一張圖來展示strucr的位域
- 下面我們看一下具體的存儲地址
isa_t初始化過程
- 我們可以通過
isa
初始化的方法initIsa
來初步了解這 64 位的 bits 的作用:
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool index, bool hasCxxDtor)
{
if (!indexed) {
isa.cls = cls;
} else {
isa.bits = ISA_MAGIC_VALUE;
isa.has_cxx_dtor = hasCxxDtor;
isa.shiftcls = (uintptr_t)cls >> 3;
}
}
-
上來就看不懂宗收,
index
是個什么漫拭,為什么在這里傳的是true
?在這里我給大家推薦一篇大神博客:Non-pointer isa- 大概的意思是在64位系統(tǒng)中混稽,為了降低內(nèi)存使用采驻,提升性能审胚,
isa
中有一部分字段用來存儲其他信息。這也解釋了上面isa_t
的那部分結(jié)構(gòu)體礼旅。
- 大概的意思是在64位系統(tǒng)中混稽,為了降低內(nèi)存使用采驻,提升性能审胚,
由于在
initInstanceIsa
方法中傳入了index = true
膳叨,初始化就分為三步
indexed
和 magic
- 初始化第一步
isa.bits = ISA_MAGIC_VALUE;
- 我們來看看
ISA_MAGIC_VALUE
的定義
#define ISA_MAGIC_VALUE 0x001d800000000001ULL
二進制表示:11101100000000000000000000000000000000000000000000001
- 我們轉(zhuǎn)換成二進制數(shù)據(jù),然后看一下哪些屬性對應的位域被這行代碼初始化了(標記為紅色)
-
從圖中了解到痘系,在使用
ISA_MAGIC_VALUE
設置isa_t
結(jié)構(gòu)體之后菲嘴,實際上只是設置了indexed
以及magic
這兩部分的值。-
其中
indexed
表示isa_t
的類型- 0 表示
raw isa
汰翠,也就是沒有結(jié)構(gòu)體的部分龄坪,訪問對象的isa
會直接返回一個指向cls
的指針,也就是在 iPhone 遷移到 64 位系統(tǒng)之前時 isa 的類型奴璃。
union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; };
- 1 表示當前
isa
不是指針悉默,但是其中也有cls
的信息城豁,只是其中關(guān)于類的指針都是保存在 shiftcls 中苟穆。
union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; struct { uintptr_t indexed : 1; uintptr_t has_assoc : 1; uintptr_t has_cxx_dtor : 1; uintptr_t shiftcls : 44; uintptr_t magic : 6; uintptr_t weakly_referenced : 1; uintptr_t deallocating : 1; uintptr_t has_sidetable_rc : 1; uintptr_t extra_rc : 8; }; };
- 0 表示
magic
的值為0x3b
用于調(diào)試器判斷當前對象是真的對象還是沒有初始化的空間
-
has_cxx_dtor
- 初始化第二步
isa.has_cxx_dtor = hasCxxDtor;
-
has_cxx_dtor
表示當前對象有 C++ 或者 ObjC 的析構(gòu)器(destructor),如果沒有析構(gòu)器就會快速釋放內(nèi)存唱星。
shiftcls
- 初始化第三步
isa.shiftcls = (uintptr_t)cls >> 3;
-
shiftcls
代表類真正的地址雳旅,將當前對象對應的類指針存入isa
結(jié)構(gòu)體中了。
將當前地址右移三位的主要原因是用于將 Class 指針中無用的后三位清除減小內(nèi)存的消耗间聊,因為類的指針要按照字節(jié)(8 bits)對齊內(nèi)存攒盈,其指針后三位都是沒有意義的 0。
- 地址填進去后哎榴,位域變化如下
- 其中紅色的為類指針型豁,這也就驗證了我們之前對于初始化
isa
時對initIsa
方法的分析是正確的。它設置了indexed
尚蝌、magic
以及shiftcls
迎变。
其他位域
在 isa_t
中,我們還有一些沒有介紹的其它 bits飘言,在這個小結(jié)就簡單介紹下這些 bits 的作用
-
has_assoc
- 對象含有或者曾經(jīng)含有關(guān)聯(lián)引用衣形,沒有關(guān)聯(lián)引用的可以更快地釋放內(nèi)存
-
weakly_referenced
- 對象被指向或者曾經(jīng)指向一個 ARC 的弱變量,沒有弱引用的對象可以更快釋放
-
deallocating
- 對象正在釋放內(nèi)存
-
has_sidetable_rc
- 對象的引用計數(shù)太大了姿鸿,存不下
-
extra_rc
- 對象的引用計數(shù)超過 1谆吴,會存在這個這個里面,如果引用計數(shù)為 10苛预,
extra_rc
的值就為 9
- 對象的引用計數(shù)超過 1谆吴,會存在這個這個里面,如果引用計數(shù)為 10苛预,
isa的應用
獲取cls地址
- 由于現(xiàn)在
isa
不在只存放地址了句狼,還多了很多附加內(nèi)容,因此需要一個專門的方法獲取shiftcls
中的內(nèi)容 - 我在前面提到了
bits
的用法热某,現(xiàn)在就是它的用武之地腻菇,它通過與ISA_MASK
按&操作突诬,就能從64位域中,獲得shiftcls
的值芜繁,也就是類的地址
#define ISA_MASK 0x00007ffffffffff8ULL
inline Class
objc_object::ISA()
{
return (Class)(isa.bits & ISA_MASK);
}
class方法
- 進入源碼以后旺隙,可以查看很多內(nèi)容的源碼
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
-
class
既是類方法又是實例方法,類方法直接返回自身骏令,實例方法返回的就是isa
中的內(nèi)容
isMemberOfClass&&isKindOfClass
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- 這也沒啥好解釋的了蔬捷,結(jié)合
class
的內(nèi)容應該很好理解了