在瞭解瞭如何在 MRC 和 ARC 兩種不一樣的環境下管理咱們的內存以後,接下來讓咱們從源碼角度看一下,蘋果是如何實現 MRC 環境中的內存管理的相關方法的。程序員
源碼下載地址:opensource.apple.com/tarballs/ob…app
先來看 alloc 方法在 NSObject 類中的實現:ide
+ (id)alloc {
return _objc_rootAlloc(self);
}
複製代碼
簡單的調用了 _objc_rootAlloc 函數,實現以下:函數
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
複製代碼
_objc_rootAlloc 則調用了 callAlloc 函數,callAlloc 實現以下:學習
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {
if (slowpath(checkNil && !cls)) return nil; // 若是 checkNil 爲 ture,且 cls 爲 nil,直接返回 nil。
#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) { // 若是 cls 沒有實現自定義 allocWithZone 方法
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
if (fastpath(cls->canAllocFast())) { // 若是 cls 支持快速 Alloc
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor(); // 獲取 cls 是否有本身的析構函數。
id obj = (id)calloc(1, cls->bits.fastInstanceSize()); // 使用 calloc 根據 fastInstanceSize 的大小申請內存空間
if (slowpath(!obj)) return callBadAllocHandler(cls); // 若是內存空間申請失敗,調用 callBadAllocHandler
obj->initInstanceIsa(cls, dtor); // 若是成功,初始化 isa
return obj; // 返回對象
}
else { // 若是 cls 不支持快速 Alloc
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0); // 經過 class_createInstance 方法直接建立對象
if (slowpath(!obj)) return callBadAllocHandler(cls); // 若是對象建立失敗,調用 callBadAllocHandler
return obj; // 返回對象
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil]; // 若是 allocWithZone 爲 true,調用 allocWithZone 建立對象
return [cls alloc]; // 不然調用 alloc 方法建立對象
}
複製代碼
整個方法的實現和代碼解讀已經寫在註釋裏了,這裏面有不少知識點值得講一講:優化
inline 是一種下降函數調用成本的方法,其本質是在調用聲明爲 inline 的函數時,會直接把函數的實現替換過去,這樣減小了調用函數的成本。固然 inline 是一種以空間換時間的作法,濫用 inline 會致使應用程序的體積增大,因此有的時候編譯器未必會真的按照你聲明 inline 的方式去用函數的實現替換函數的調用。ui
ALWAYS_INLINE 宏如其名,會強制開啓 inline,其實現以下:this
#define ALWAYS_INLINE inline __attribute__((always_inline))
複製代碼
這兩個宏的實現以下:atom
#define slowpath(x) (__builtin_expect(bool(x), 0))
#define fastpath(x) (__builtin_expect(bool(x), 1))
複製代碼
這兩個宏都使用了一個叫作 __builtin_expect 的函數:spa
long __builtin_expect (long EXP, long C)
複製代碼
它的返回值就是整個函數的返回值,參數 C 表明預計的值,表示程序員知道 EXP 的值極可能就是 C。
所以,在蘋果定義的兩個宏中,fastpath(x) 依然返回 x,只是告訴編譯器 x 的值通常不爲 0,從而編譯器能夠進行優化。同理,slowpath(x) 表示 x 的值極可能爲 0,但願編譯器進行優化。
由於計算機每次不會只讀取一條語句,因此上述兩個宏在 if 條件判斷中頗有用,有助於編譯器優化代碼,以減小指令重讀。
用於判斷當前使用的語言是不是 Objective-C 2.0
在以前的方法中,cls 的 isa 是經過 cls->ISA() 獲取的,在解讀方法的實現以前,先來學習一下 SUPPORT_NONPOINTER_ISA 宏的含義。
// Define SUPPORT_NONPOINTER_ISA=1 on any platform that may store something
// in the isa field that is not a raw pointer.
#if !SUPPORT_INDEXED_ISA && !SUPPORT_PACKED_ISA
# define SUPPORT_NONPOINTER_ISA 0
#else
# define SUPPORT_NONPOINTER_ISA 1
#endif
複製代碼
SUPPORT_NONPOINTER_ISA 用於標記是否支持優化的 isa 指針,其字面含義意思是 isa 的內容再也不是類的指針了,而是包含了更多信息,好比引用計數,析構狀態,被其餘 weak 變量引用狀況。而 SUPPORT_NONPOINTER_ISA 宏的定義又涉及到了其餘兩個宏:SUPPORT_INDEXED_ISA 和 SUPPORT_PACKED_ISA:
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa
// field as an index into a class table.
// Note, keep this in sync with any .s files which also define it.
// Be sure to edit objc-abi.h as well.
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
// Define SUPPORT_PACKED_ISA=1 on platforms that store the class in the isa
// field as a maskable pointer with other data around it.
#if (!__LP64__ || TARGET_OS_WIN32 || \ (TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC))
# define SUPPORT_PACKED_ISA 0
#else
# define SUPPORT_PACKED_ISA 1
#endif
複製代碼
從註釋上來看,SUPPORT_INDEXED_ISA 的含義是,若是爲 1,則會在 isa field 中存儲 class 在 class table 的索引。SUPPORT_PACKED_ISA 爲 1 時則是在 isa field 中經過 mask 的方式存儲 class 的指針信息。
接下來讓咱們來看一下 ISA 方法的實現:
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer()); // 要確保該類不是 tagged pointer
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) { // 若是 isa 是非指針型 isa
uintptr_t slot = isa.indexcls; // 獲取 class 索引
return classForIndex((unsigned)slot); // 從 class table 中查找並返回 isa 信息
}
return (Class)isa.bits; // 直接返回 isa.bits
#else
return (Class)(isa.bits & ISA_MASK); // 經過掩碼獲取 bits 中對應的信息並返回
#endif
}
複製代碼
hasCustomAWZ 方法的實現以下
bool hasCustomAWZ() {
return ! bits.hasDefaultAWZ();
}
複製代碼
hasDefaultAWZ 方法的實現以下:
bool hasDefaultAWZ() {
return data()->flags & RW_HAS_DEFAULT_AWZ;
}
複製代碼
而 data 方法的實現是:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
複製代碼
總而言之就是從 bits 中根據位掩碼 FAST_DATA_MASK 獲取 data,再根據位掩碼 RW_HAS_DEFAULT_AWZ 獲取是不是默認的 allocWithZone 方法。
canAllocFast 使用來獲取類是否支持快速 alloc 的方法,其實現以下:
bool canAllocFast() {
assert(!isFuture());
return bits.canAllocFast();
}
複製代碼
咱們先來看 isFuture 的實現:
// Returns true if this is an unrealized future class.
// Locking: To prevent concurrent realization, hold runtimeLock.
bool isFuture() {
return data()->flags & RW_FUTURE;
}
複製代碼
bits 的 canAllocFast 方法實現以下,一樣是經過位掩碼 FAST_ALLOC 從 bits 中獲取信息:
// summary bit for fast alloc path: !hasCxxCtor and
// !instancesRequireRawIsa and instanceSize fits into shiftedSize
#define FAST_ALLOC (1UL<<2)
#if FAST_ALLOC
bool canAllocFast() {
return bits & FAST_ALLOC;
}
#else
bool canAllocFast() {
return false;
}
#endif
複製代碼
hasCxxDtor 方法的做用是獲取當前 Class 是否有本身的析構函數。hasCxxDtor 方法用來獲取類及父類是否有本身的析構函數,與對象是否有實例變量有關,會記錄在對象的 isa 內。實現以下:
bool hasCxxDtor() {
// addSubclass() propagates this flag from the superclass.
assert(isRealized());
return bits.hasCxxDtor();
}
複製代碼
isRealized 相似與 isFuture,實現以下:
// Locking: To prevent concurrent realization, hold runtimeLock.
bool isRealized() {
return data()->flags & RW_REALIZED;
}
複製代碼
bits 的 hasCxxDtor 方法實現以下:
// class or superclass has .cxx_destruct implementation
#define FAST_HAS_CXX_DTOR (1UL<<51)
#if FAST_HAS_CXX_DTOR
bool hasCxxDtor() {
return getBit(FAST_HAS_CXX_DTOR);
}
#else
bool hasCxxDtor() {
return data()->flags & RW_HAS_CXX_DTOR;
}
#endif
複製代碼
接着,經過 calloc 函數,用來動態地分配內存空間並初始化爲 0。calloc 函數原型以下:
void *calloc(size_t __count, size_t __size) 複製代碼
calloc 在內存中動態地分配 num 個長度爲 size 的連續空間,並將每個字節都初始化爲 0。因此它的結果是分配了 num * size 個字節長度的內存空間,而且每一個字節的值都是0。分配成功返回指向該內存的地址,失敗則返回 NULL。
cls->bits.fastInstanceSize() 是用來獲取實例佔用存儲空間的方法,實現以下:
size_t fastInstanceSize()
{
assert(bits & FAST_ALLOC);
return (bits >> FAST_SHIFTED_SIZE_SHIFT) * 16;
}
複製代碼
接着會判斷對象是否建立成功,若是沒能建立成功會調用 callBadAllocHandler(cls),
static id defaultBadAllocHandler(Class cls)
{
_objc_fatal("attempt to allocate object of class '%s' failed",
cls->nameForLogging());
}
static id(*badAllocHandler)(Class) = &defaultBadAllocHandler;
static id callBadAllocHandler(Class cls)
{
// fixme add re-entrancy protection in case allocation fails inside handler
return (*badAllocHandler)(cls);
}
複製代碼
若是對象建立成功,會使用 initInstanceIsa 爲其初始化 isa,代碼以下:
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; // 若是 nonpointer 爲 false,則直接把 cls 賦值給 cls
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0); // 初始化新的 isa
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE; // 對 isa 中的一些值進行初始化
// 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; // 將 newisa 賦值給 isa
}
}
複製代碼
若是類不支持 AllocFast,則須要經過 class_createInstance 方法進行對象的建立,方法的實現以下:
id class_createInstance(Class cls, size_t extraBytes) {
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
/*********************************************************************** * class_createInstance * fixme * Locking: none **********************************************************************/
static __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
assert(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cls->hasCxxCtor(); // 獲取 cls 及其父類是否有構造函數
bool hasCxxDtor = cls->hasCxxDtor(); // 獲取 cls 及其父類是否有析構函數
bool fast = cls->canAllocNonpointer(); // 是對 isa 的類型的區分,若是一個類和它父類的實例不能使用 isa_t 類型的 isa 的話,返回值爲 false,可是在 Objective-C 2.0 中,大部分類都是支持的
size_t size = cls->instanceSize(extraBytes); // 獲取須要申請的空間大小
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) { // 若是 zone 參數爲空,且支持 fast,經過 calloc 申請空間並初始化 isa
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
}
else {
if (zone) { // 若是 zone 不爲空,使用 malloc_zone_calloc 方法申請空間
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
} else { // 若是 zone 爲空,使用 calloc 方法申請空間
obj = (id)calloc(1, size);
}
if (!obj) return nil;
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls); // 初始化 isa
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls); // 構建對象
}
return obj;
}
複製代碼
若是是非 Objective-C 2.0 的代碼,且 allocWithZone 爲 true,會經過 allocWithZone 方法初始化對象,其實現以下所示:
// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
id obj;
#if __OBJC2__
// allocWithZone under __OBJC2__ ignores the zone parameter
(void)zone;
obj = class_createInstance(cls, 0);
#else
if (!zone) {
obj = class_createInstance(cls, 0);
}
else {
obj = class_createInstanceFromZone(cls, 0, zone);
}
#endif
if (slowpath(!obj)) obj = callBadAllocHandler(cls);
return obj;
}
複製代碼
再講完 alloc 後,new 的實現就很簡單了,以下所示:
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
複製代碼
就是嵌套調用了 alloc 和 init。
copy 和 mutableCopy 的實現就很簡單了,它們只是調用了 NSCopying 協議中的方法:
- (id)copy {
return [(id)self copyWithZone:nil];
}
- (id)mutableCopy {
return [(id)self mutableCopyWithZone:nil];
}
複製代碼
至此,關於 alloc、new、copy 和 mutableCopy 方法的實現就已經分析完了,關於類的一些細節,能夠參考擴展閱讀裏的文章進行更深一步的學習。
擴展閱讀: