前言
一步一個腳印地探索iOS的OC底層原理坛猪,通過前面的文章可以大概了解了OC對象創(chuàng)建的alloc原理
和OC對象的內存字節(jié)對齊
最楷,但是這也只是知道了對象創(chuàng)建的底層過程和開辟內存空間的,這篇文章將介紹對象的本質
和對象與類的關聯(lián)---isa
1.isa的初始化
isa指針:在Objective-C中拙寡,任何類的定義都是對象庐橙。類和類的實例(對象)沒有任何本質上的區(qū)別。任何對象都有isa指針怯伊。也就是說在對象創(chuàng)建的時候就會有isa指針初始化了真慢。為了搞清楚還是需要用到OC對象創(chuàng)建的alloc原理里面源碼的_class_createInstanceFromZone
的方法的部分源碼包各,然后跟著流程進去得到如下的部分源碼
//_class_createInstanceFromZone的部分代碼
//初始化實例的isa指針
obj->initInstanceIsa(cls, hasCxxDtor);
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) {
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;
}
}
其中的nonpointer
字面的意思是沒有指針的,一般情況下nonpointer是為true的,只有在例如實現(xiàn)了allocwithzone方法胎挎,retain,release等的時候會是false始赎。如果為false是直接將傳進來的cls為isa的關聯(lián)的cls賦值邦泄。
其他的剩下的部分就是對isa
的初始化賦值了。但是具體的isa
內部是怎樣的還是不知道的砸彬,從源碼中isa_t
點擊進去可以查看颠毙。
2.isa的內部結構
通過源碼可以知道isa
的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
};
#ifndef _UINTPTR_T
#define _UINTPTR_T
typedef unsigned long uintptr_t;
#endif /* _UINTPTR_T */
從中可以知道,isa
是一個聯(lián)合體(union),里面有關聯(lián)的類cls和long型的bits砂碉。
什么是聯(lián)合體(union)呢蛀蜜?聯(lián)合體是一種特殊的類,也是一種構造類型的數(shù)據(jù)結構增蹭。完全就是共用一個內存首地址滴某,并且各種變量名都可以同時使用,操作也是共同生效沪铭。所以也叫
共用體
壮池。并且聯(lián)合體(union)中是各變量是“互斥”的
,但是內存使用更為精細靈活杀怠,也節(jié)省了內存空間椰憋。
由上面的概念可以知道,cls
和bits
之間是互斥的赔退,即有cls
就沒有bits
,有bits
就沒有cls
橙依。這就很好地解釋了為什么上面的源碼在初始化isa
的時候會用nonpointer
來區(qū)分開证舟。所以isa
的大小占8個字節(jié),64位窗骑。其中這64位中分別存儲了什么呢女责?通過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
# 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
這兩種是分別在arm64
和x86
系統(tǒng)架構下的,但是都是64位的创译,本文的說明是在x86
下介紹的抵知。
- nonpointer:占1位,表示是否對
isa
指針開啟指針優(yōu)化软族,0:表示純指針刷喜;1:表示不止是類對象地址,isa中包含了類信息立砸、對象的引用計數(shù)等掖疮。 - has_assoc:占1位,表示關聯(lián)對象標志位颗祝,0沒有浊闪,1存在。
- has_cxx_dtor:占1位螺戳,表示該對象是否有C++或者Objc的析構器搁宾,如果有析構函數(shù),則需要做析構邏輯温峭,如果沒有則可以更快的釋放對象猛铅。
- shiftcls:占44位,存儲類指針的值凤藏。開啟指針優(yōu)化的情況下,在arm64架構中有33位用來存儲類指針堕伪。(由上面的初始化源碼也可以很好的說明,關聯(lián)的類指針向右移3位)
newisa.shiftcls = (uintptr_t)cls >> 3;
- magic:占6位揖庄,用于調試器判斷當前對象是真的對象還是沒有初始化的空間。
- weakly_referenced:占1位欠雌,表示對象是否被指向或者曾經(jīng)指向一個ARC的弱變量蹄梢,沒有弱引用的對象可以更快釋放。
- deallocating:占1位富俄,表示對象是否正在釋放內存禁炒。
- has_sidetable_rc:占1位,表示當對象引用技術大于10時霍比,則需要借用該變量存儲進位幕袱。
- extra_rc:占8位,表示該對象的引用計數(shù)值悠瞬,實際上是引用計數(shù)總值減1们豌。例如涯捻,如果對象的引用計數(shù)為10,那么extra_rc為9望迎,如果引用計數(shù)大于10障癌,則需要使用到下面的has_sidetable_rc。
3.isa是對象與類的連接
為了方便介紹下面的內容需要定義一個什么屬性都沒有的TestJason類辩尊,然后通過objc4-756.2
蘋果官方的源碼涛浙。通過object_getClass
這個方法可以獲取到類。
TestJason *test2 = [TestJason alloc];
Class testClass = object_getClass(test2);
NSLog(@"%@",test2);
通過源碼找到object_getClass
的方法
/***********************************************************************
* object_getClass.
* Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) {
uintptr_t slot =
(ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else {
uintptr_t slot =
(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}
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
}
# define SUPPORT_INDEXED_ISA 0
# define ISA_MASK 0x00007ffffffffff8ULL
從源碼中可以知道返回的isa
最終是(Class)(isa.bits & ISA_MASK)
摄欲。
其中源碼有一個判斷isTaggedPointer()
,其中蘋果對于Tagged Pointer
的概念引入轿亮,是為了節(jié)省內存和提高執(zhí)行效率,對于 64 位程序蒿涎,相關邏輯能減少一半的內存占用哀托,以及 3 倍的訪問速度提升,100 倍的創(chuàng)建劳秋、銷毀速度提升仓手。如果想了解這部分的內容可以看看深入理解 Tagged Pointer
。
下面就是用lldb
的指令來驗證一下的玻淑。首先用x/4gx test2
來打印出地址
然后用
p/x TestJason.class
打印出TestJason
類的內存值
由源碼知道類Class的最終返回是
(Class)(isa.bits & ISA_MASK)
的嗽冒,所以將x/4gx test2
打印出來的0x001d800100001749
& ISA_MASK
的值得到如下:
最終發(fā)現(xiàn)
$3
和$4
的內存值是一樣的,所以isa
是關聯(lián)著對象與類的补履。由前面的文章知道由于內存的優(yōu)化對象的其他屬性的位置實際會發(fā)生變化的添坊,所以對象的第一個屬性就是isa
。
4.isa的走位原理
通過上面的介紹箫锤,可以知道了isa是關聯(lián)著對象與類的贬蛙,并且對象的isa指向類,因為萬物皆對象谚攒,那么類的isa指向的是誰呢阳准?可以通過蘋果官方的isa的走位流程圖
isa流程圖.png
其中虛線是代表
isa
的走位,實線代表的是繼承關系
的走位馏臭。圖中有一個meta class
元類的概念野蝇。
什么是元類?在OC中括儒,對象的方法并沒有存儲于對象的結構體中(如果每一個對象都保存了自己能執(zhí)行的方法绕沈,那么對內存的占用有極大的影響)。
當對象的實例方法被調用時帮寻,它通過自己的isa來查找對應的類乍狐,然后在所屬類的 class_data_bits_t結構體中查找對應方法的實現(xiàn)。同時规婆,每一個objc_class 也有一個指向自己的父類的指針superclass用來查找繼承的方法澜躺。
而當調用 類方法 時蝉稳,它的查找流程是怎樣的呢?對此OC的解決方案就是引入元類掘鄙,來保證類方法也能通過相同的機制查找到耘戚。對于元類的解釋轉自OC源碼分析之isa
是不是看著這張圖的各種箭頭指向一臉懵逼呢?下面就是來對這張圖的isa
的走位驗證一下操漠。
4.1 isa的走位
還是用TestJason
這個什么屬性都沒有的類來介紹收津。通過lldb
的指令來驗證。由上面的圖可以知道浊伙,對象的isa
指向類撞秋,類的isa
指向元類。
由圖可以知道嚣鄙,先用
x/4gx test2
來打印出isa
的內存值吻贿,然后用isa
的內存值&ISA_MASK
得到$9
,然后po $9
此時得到的是類。然后再用x/4gx
來打印$9
的值哑子,此時就是相當于打印出類的isa
的內存值了舅列。最后可以看到兩個TestJason
,但是這兩個的內存值是不一樣的卧蜓,分別是類和元類帐要。但是中可以看到元類
里面還有isa
值,那么就繼續(xù)打印弥奸。
從中可以看到榨惠,元類的
isa
指向了NSObject
,但是這個NSObject
到底是類呢盛霎?還是元類呢赠橙?為了搞清楚,可以用x/4gx NSObject.class
來打印類的isa
來驗證愤炸。
從中可以看到
$13
打印的內存值是等于$16
的简烤,那么就可以知道元類
的isa
指向的是根元類
的isa
。那么根元類
的isa
指向誰呢摇幻?繼續(xù)打印
從中可以看到
根元類
的isa
指向了它的本身,這就形成了一個閉環(huán)挥萌。所以整體來說的話绰姻,就是
對象的isa-->類的isa-->元類的isa-->根元類的isa-->根元類的isa(根元類本身)
這就很好地驗證了蘋果的官網(wǎng)的isa
走位圖。
4.2 對象引瀑,類之間的繼承的走位
為了介紹方便狂芋,添加多一個TestSuperJason
的類,并且讓TestJason
是繼承TestSuperJason
的憨栽。還是通過lldb
的指令來的打印class_getSuperclass
方法帜矾。
這是類的繼承關系的打印
這是元類的繼承關系打印
所以它們的繼承關系是
類:
TestJason-->TestSuperJason-->NSObject-->nil
元類:
TestJason元類-->TestSuperJason元類-->NSObject元類-->NSObject類-->nil
5.對象的本質
通過上面的知識可以大概了解了isa
的原理翼虫,但是對象的本質是什么還不是很了解的,可以通過clang
編譯成cpp
文件來查看屡萤。實現(xiàn)的代碼如下:
@interface Jason : NSObject{
NSString *nickName;
}
@property (nonatomic, copy) NSString *name;
@end
@implementation Jason
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"123");
}
return 0;
}
在文件的路徑下珍剑,可以終端輸入命令,就可以查看main.cpp文件
clang -rewrite-objc main.m -o main.cpp
從中可以看到
#ifndef _REWRITER_typedef_Jason
#define _REWRITER_typedef_Jason
typedef struct objc_object Jason;
typedef struct {} _objc_exc_Jason;
#endif
extern "C" unsigned long OBJC_IVAR_$_Jason$_name;
struct Jason_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *nickName;
NSString *_name;
};
// @property (nonatomic, copy) NSString *name;
/* @end */
// @implementation Jason
static NSString * _I_Jason_name(Jason * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Jason$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_Jason_setName_(Jason * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Jason, _name), (id)name, 0, 1); }
// @end
struct NSObject_IMPL {
Class isa;
};
從中可以看到,對象最終會被編譯成結構體struct
,NSObject_IMPL
里面包含著isa
,在類里面定義的屬性name
和成員變量nickName
也是在Jason_IMPL
結構體里面死陆,但是屬性變量name
是有getter
方法_I_Jason_name
和setter
方法_I_Jason_setName_
的招拙,而成員變量nickName
是沒有的。并且這些方法里面都有默認帶有兩個參數(shù)id self
和SEL _cmd
,這樣就很好解釋了我們在方法中可以直接調用self
措译。
5.最后
通過上面的內容可以了解到isa
是一個isa_t
類型的聯(lián)合體(union),并且里面的屬性是互斥的别凤,isa
的大小占8字節(jié)。一般情況下都是在bits
下的位域來存儲內容领虹,其中ISA_BITFIELD
在x86
和arm64
架構下都是64位规哪,但是里面的屬性的占位有點區(qū)別的。isa
是關聯(lián)著對象與類的塌衰,并且對象的本質就是一個結構體诉稍。至此有關isa
的原理介紹到此結束。