一步一個腳印地探索iOS的OC底層原理,經過前面的文章能夠大概瞭解了OC對象建立的alloc原理和OC對象的內存字節對齊,可是這也只是知道了對象建立的底層過程和開闢內存空間的,這篇文章將介紹
對象的本質
和對象與類的關聯---isabash
isa指針:在Objective-C中,任何類的定義都是對象。類和類的實例(對象)沒有任何本質上的區別。任何對象都有isa指針。也就是說在對象建立的時候就會有isa指針初始化了。爲了搞清楚仍是須要用到OC對象建立的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的,只有在例如實現了allocwithzone方法,retain,release等的時候會是false。若是爲false是直接將傳進來的cls爲isa的關聯的cls賦值。架構
其餘的剩下的部分就是對isa
的初始化賦值了。可是具體的isa
內部是怎樣的仍是不知道的,從源碼中isa_t
點擊進去能夠查看。ide
經過源碼能夠知道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
是一個聯合體(union),裏面有關聯的類cls和long型的bits。源碼分析
什麼是聯合體(union)呢?聯合體是一種特殊的類,也是一種構造類型的數據結構。徹底就是共用一個內存首地址,而且各類變量名均可以同時使用,操做也是共同生效。因此也叫
共用體
。而且聯合體(union)中是各變量是「互斥」的
,可是內存使用更爲精細靈活,也節省了內存空間。post
由上面的概念能夠知道,cls
和bits
之間是互斥的,即有cls
就沒有bits
,有bits
就沒有cls
。這就很好地解釋了爲何上面的源碼在初始化isa
的時候會用nonpointer
來區分開。因此isa
的大小佔8個字節,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
系統架構下的,可是都是64位的,本文的說明是在x86
下介紹的。ui
isa
指針開啓指針優化,0:表示純指針;1:表示不止是類對象地址,isa中包含了類信息、對象的引用計數等。newisa.shiftcls = (uintptr_t)cls >> 3;
複製代碼
爲了方便介紹下面的內容須要定義一個什麼屬性都沒有的TestJason類,而後經過objc4-756.2
蘋果官方的源碼。經過object_getClass
這個方法能夠獲取到類。this
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
的概念引入,是爲了節省內存和提升執行效率,對於 64 位程序,相關邏輯能減小一半的內存佔用,以及 3 倍的訪問速度提高,100 倍的建立、銷燬速度提高。若是想了解這部分的內容能夠看看深刻理解 Tagged Pointer 。
下面就是用lldb
的指令來驗證一下的。首先用x/4gx test2
來打印出地址
p/x TestJason.class
打印出
TestJason
類的內存值
由源碼知道類Class的最終返回是
(Class)(isa.bits & ISA_MASK)
的,因此將
x/4gx test2
打印出來的
0x001d800100001749
&
ISA_MASK
的值獲得以下:
最終發現
$3
和
$4
的內存值是同樣的,因此
isa
是關聯着對象與類的。由前面的文章知道因爲內存的優化對象的其餘屬性的位置實際會發生變化的,因此對象的第一個屬性就是
isa
。
經過上面的介紹,能夠知道了isa是關聯着對象與類的,而且對象的isa指向類,由於萬物皆對象,那麼類的isa指向的是誰呢?能夠經過蘋果官方的isa的走位流程圖 isa流程圖.png
其中虛線是表明isa
的走位,實線表明的是
繼承關係
的走位。圖中有一個
meta class
元類的概念。
什麼是元類?在OC中,對象的方法並無存儲於對象的結構體中(若是每個對象都保存了本身能執行的方法,那麼對內存的佔用有極大的影響)。 當對象的實例方法被調用時,它經過本身的isa來查找對應的類,而後在所屬類的 class_data_bits_t結構體中查找對應方法的實現。同時,每個objc_class 也有一個指向本身的父類的指針superclass用來查找繼承的方法。 而當調用 類方法 時,它的查找流程是怎樣的呢?對此OC的解決方案就是引入元類,來保證類方法也能經過相同的機制查找到。對於元類的解釋轉自OC源碼分析之isa
是否是看着這張圖的各類箭頭指向一臉懵逼呢?下面就是來對這張圖的isa
的走位驗證一下。
仍是用TestJason
這個什麼屬性都沒有的類來介紹。經過lldb
的指令來驗證。由上面的圖能夠知道,對象的isa
指向類,類的isa
指向元類。
x/4gx test2
來打印出
isa
的內存值,而後用
isa
的內存值&
ISA_MASK
獲得
$9
,而後
po $9
此時獲得的是類。而後再用
x/4gx
來打印
$9
的值,此時就是至關於打印出類的
isa
的內存值了。最後能夠看到兩個
TestJason
,可是這兩個的內存值是不同的,分別是類和元類。可是中能夠看到
元類
裏面還有
isa
值,那麼就繼續打印。
從中能夠看到,元類的
isa
指向了
NSObject
,可是這個
NSObject
究竟是類呢?仍是元類呢?爲了搞清楚,能夠用
x/4gx NSObject.class
來打印類的
isa
來驗證。
從中能夠看到
$13
打印的內存值是等於
$16
的,那麼就能夠知道
元類
的
isa
指向的是
根元類
的
isa
。那麼
根元類
的
isa
指向誰呢?繼續打印
從中能夠看到
根元類
的
isa
指向了它的自己,這就造成了一個閉環。因此總體來講的話,就是
對象的isa-->類的isa-->元類的isa-->根元類的isa-->根元類的isa(根元類自己)
複製代碼
這就很好地驗證了蘋果的官網的isa
走位圖。
爲了介紹方便,添加多一個TestSuperJason
的類,而且讓TestJason
是繼承TestSuperJason
的。仍是經過lldb
的指令來的打印class_getSuperclass
方法。
這是類的繼承關係的打印
這是元類的繼承關係打印 因此它們的繼承關係是類:
TestJason-->TestSuperJason-->NSObject-->nil
元類:
TestJason元類-->TestSuperJason元類-->NSObject元類-->NSObject類-->nil
複製代碼
經過上面的知識能夠大概瞭解了isa
的原理,可是對象的本質是什麼還不是很瞭解的,能夠經過clang
編譯成cpp
文件來查看。實現的代碼以下:
@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
是沒有的。而且這些方法裏面都有默認帶有兩個參數id self
和SEL _cmd
,這樣就很好解釋了咱們在方法中能夠直接調用self
。
經過上面的內容能夠了解到isa
是一個isa_t
類型的聯合體(union),而且裏面的屬性是互斥的,isa
的大小佔8字節。通常狀況下都是在bits
下的位域來存儲內容,其中ISA_BITFIELD
在x86
和arm64
架構下都是64位,可是裏面的屬性的佔位有點區別的。isa
是關聯着對象與類的,而且對象的本質就是一個結構體。至此有關isa
的原理介紹到此結束。