主動已經是我對熱愛東西表達的極限了
對象的本質?
聯(lián)合體位域的簡析?
isa的結構信息?
isa如何關聯(lián)類?
通過位運算驗證關聯(lián)類
總結。
什么是對象?
對象在底層變成了什么呢?-
什么是Clang?
Clang是一個由Apple主導編寫,基于LLVM的C/C++/Objective-C編譯器
-
用Clang做些什么?
Clang 通過底層編譯,將一些m文件編譯為cpp。 因為OC為C++或者C的超集,通過Clang底層編譯,可以更多的看到底層的實現(xiàn)原理與邏輯和底層的架構
-
如何使用Clang?
Clang
終端操作四種命令如下:
clang -rewrite-objc main.m -o main.cpp 把目標文件編譯成c++文件
`xcode`安裝的時候順帶安裝了`xcrun`命令,`xcrun`命令在`clang`的基礎上進行了 一些封裝,要更好用一些
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模擬器)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp (手機)
Clang 具體操作請查看:后續(xù)
通過Clang
編譯查看類
的底層是如何實現(xiàn)的,首先定義如下:
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!"); }
return 0;
}
找到已經編譯好的main.cpp
文件,搜索LGPerson
,得到我想要的信息如下:
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
// @property (nonatomic, copy) NSString *name;
/* @end */
// @implementation LGPerson
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) {
return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name));
}
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) {
objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1);
}
// @end
-
對象
在底層會被編譯為struct
那對象
是如何被編譯為結構體
的呢?
我們知道結構體在C++可以繼承,C可以偽繼承
偽繼承方式:
直接將NSObject
結構體定義為LGPerson
中的第一個屬性
,意味著LGPerson
擁有NSObject
中的所有成員變量
。
LGPerson
中的第一個屬性NSObject_IVARS
等效于NSObject中
的isa
這里將LGPerson_IMPL
結構體中的第一屬性
命名為當前結構體,即LGPerson_IMPL
擁有 NSObject
中的所有成員變量
。而NSObject_IVARS
即isa
而LGPerson
類中的name
的get, set
方法也是一一對應的:
而set
方法調用了objc_setProperty
,通過分析得出objc_setProperty
采用工廠模式:return newValue
, remove oldValue
;意味著任何類中的set
方法都會執(zhí)行了如下操作:
繼續(xù)分析isa結構,OC底層原理 結構體&聯(lián)合體
-
isa結構分析:
在之前探索 OC底層原理 alloc & init & new 篇 時提到過initInstanceIsa
方法,通過initInstanceIsa
為切入點來分析isa
是如何初始化的,可以看到isa
指針的類型isa_t
定義是通過聯(lián)合體(union)來定義
,聯(lián)合體
不清楚的小伙伴請參考:OC底層原理 結構體&聯(lián)合體
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls; //這里返回的cls 為什么會是一個class類型?請查看后續(xù)
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
這里的isa_t
為什么要定義成聯(lián)合體
呢?是為了優(yōu)化內存
,而isa
指針占用的內存大小為8
字節(jié),即64
位,已經能夠存儲大量信息了,這樣可以節(jié)省內存空間,以提高性能的目的
而isa_t
的定義中可以看出,
通過cls
初始化,bits
無默認值
通過bits
初始化,cls
有默認值
體現(xiàn)了聯(lián)合體
的互斥性
通過宏ISA_BITFIELD
可以看出isa
在iOS( __arm64__)
和macOS(__x86_64__)
的計算是存在差異
的
-
isa
的結構信息對應如下:nonpointer
:表示是否對isa
指針開啟指針優(yōu)化
0
:純isa
指針;
1
:不止是類對象地址,isa
中包含了類信息、對象的引用計數等has_assoc
:關聯(lián)對象標志位,
0
沒有關聯(lián)對象
1
存在關聯(lián)對象has_cxx_dtor
:該對象是否有 C++ 或者 Objc 的析構器(類似于dealloc
)
如果有
析構函數,則需要做析構邏輯
如果沒有
,則可以更快的釋放
對象shiftcls:
存儲類指針的值(類的地址
), 即類信息
。開啟指針優(yōu)化
的情況下,
在__arm64__
架構中有33
位用來存儲類指針。
在__x86_64__
架構中有44
位用來存儲類指針。magic
:用于調試器判斷當前對象是真的對象
還是沒有初始化的空間
weakly_referenced
:志對象是否被指向
或者曾經指向一個 ARC 的弱變量
沒有弱引用的對象
可以更快釋放。deallocating
:標志對象是否正在釋放
內存has_sidetable_rc
:當對象引用計數大于 10
時,則需要借用該變量存儲進位
extra_rc
:當表示該對象的引用計數值
,實際上是引用計數值減 1
, 例如,如果對象的引用計數為 10
,那么extra_rc
為9
。如果引用計數大于 10
, 則需要使用has_sidetable_rc
更直觀的isa
的結構信息圖如下:
通過 initIsa
方法,查看 isa
指針是如何被初始化
的
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);//isa初始化
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0); //isa初始化
#if SUPPORT_INDEXED_ISA // !nonpinter 執(zhí)行,即isa通過cls定義
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 // bits 時執(zhí)行的流程
newisa.bits = ISA_MAGIC_VALUE; // bits進行賦值
// 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與類關聯(lián)
#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;
}
}
-
initIsa
方法主要使用:cls
初始化isa
,和bits
初始化isa
執(zhí)行代碼,開始驗證,以LGPerson
為例:
對賦值newisa.bits
賦值之后打印出newisa
查看LLDB結果
- 對賦值
newisa.bits
賦值會對cls
進行追加;對cls
賦值不會同時對bits
進行賦值(聯(lián)合體互斥性
)
通過LLDB打印結果,在進行賦值的時候,bits
中的magic
會存在一個59
呢?
使用計算器查看宏ISA_MAGIC_VALUE
與59
的2進制
magic
的占位為6
,從47~52
表示magic
的占位,所以magic
的默認值為59
,二進制
表示;也可以理解為isa
指針將magic
的占位為6
,轉換為2進制
存儲
- isa與類關聯(lián)
當執(zhí)行完如下代碼后,LLDB打印出當前newisa
,
cls
與 isa
關聯(lián)原理就是isa
指針中的shiftcls
位域中存儲了類信息
,其中initInstanceIsa
的過程是將 calloc
指針 和當前的 類cls
關聯(lián)起來。而newisa.shiftcls = (uintptr_t)cls >> 3
是將當前cls 強轉
為系統(tǒng)能夠編譯的0
,1
識別碼。然后右移3
位。之所以要移動3位,要知道shiftcls
才是我們需要存儲的類信息
,從上面isa對照表
中可以看出,前面三位并不是我們需要的類內容
,需要右移3位
進行抹0
操作。
newisa.shiftcls = (uintptr_t)cls >> 3; //isa與類關聯(lián)
這個時候我們已經知道isa
與類
進行了關聯(lián)。
-
開始驗證
isa
中的shiftcls
是否真正的存儲了當前類信息
將initInstanceIsa
方法返回出去,然后開始驗證:
-
方式1. 通過
isa
指針地址與宏ISA_MSAK
的值 通過位運算&
來驗證
LLDB 操作:# define ISA_MASK 0x00007ffffffffff8ULL //__x86_64__ # define ISA_MASK 0x0000000ffffffff8ULL //__arm64__
po obj
,然后x/4gx LGPerson
的地址 或者直接x/4gx
,然后取LGPerson
的isa
地址指針&
宏ISA_MASK
,結果如下:
位運算驗證 -
方式2. 使用位運算驗證
shiftcls
存儲在當前3~46
位,
向右
移動3
位,抹除0~2
號位數據,
向左
移動20
位,移除47~64
號位的數據,
向右
移動17
位,回到原來的shiftcls
所在的存儲位,直接取出,即LGPerson 類信息
位運算操作截圖 -
方式3. 通過
在runtime
的object_getClass
來驗證main.m
導入頭文件#import <objc/runtime.h>
,然后直接打印出當前的類也能夠驗證,這里就不打印了,直接去查看object_getClass
的實現(xiàn),
object_getClass
方法如下:
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
我們進入getIsa
方法繼續(xù)查看,
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA(); //直接return了當前isa
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
繼續(xù)查看ISA()
做了哪些事情,
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); //這里與我們方式1做的操作是一樣的
#endif
}
我們可以看出object_getClass
的內部實現(xiàn),也是采用了位運算&
的方式對當前類進行處理。
驗證總結:驗證isa與類是否關聯(lián)還有其他方式,這里不一一闡述,有興趣的同學可以自己探索。
-
isa結構分析總結:
1.isa
通過isa_t
方法進行初始化,其中使用到了聯(lián)合體位域
;isa
與類
關聯(lián)的同時,做了強轉
的操作和位運算計算存儲
,其中newisa.shiftcls = (uintptr_t)cls >> 3;
的uintptr_t
表示long
類型。
2.x86_64
和arm64
環(huán)境不同,存在部分計算差異
3.掌握如何對類
和isa
的關聯(lián)驗證
4.如何使用Clang
查看源碼。