前言
在物件導向程式設計中,我們每天都在建立物件,用物件描述著整個世界,然而物件是如何從孕育到銷燬的呢?
目錄
- 1.孕育物件
- 2.物件的出生
- 3.物件的成長
- 4.物件的銷燬
- 5.總結
一.孕育物件
每天開發我們都在alloc物件,而alloc方法做了些什麼呢?
1 2 3 |
+ (id)alloc { return _objc_rootAlloc(self); } |
所有物件alloc都會呼叫這個root的方法
1 2 3 4 |
id _objc_rootAlloc(Class cls) { return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); } |
這個方法又會去呼叫callAlloc方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { if (checkNil && !cls) return nil; #if __OBJC2__ if (! cls->ISA()->hasCustomAWZ()) { // 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 (cls->canAllocFast()) { // No ctors, raw isa, etc. Go straight to the metal. bool dtor = cls->hasCxxDtor(); id obj = (id)calloc(1, cls->bits.fastInstanceSize()); if (!obj) return callBadAllocHandler(cls); obj->initInstanceIsa(cls, dtor); return obj; } else { // Has ctor or raw isa or something. Use the slower path. id obj = class_createInstance(cls, 0); if (!obj) return callBadAllocHandler(cls); return obj; } } #endif // No shortcuts available. if (allocWithZone) return [cls allocWithZone:nil]; return [cls alloc]; } |
由於入參 checkNil = false,所以不會返回nil。
1 2 3 |
bool hasCustomAWZ() { return ! bits.hasDefaultAWZ(); } |
在這張圖,我們可以看到在物件的資料段data中,class_rw_t中有一個flags。
1 2 3 4 5 |
bool hasDefaultAWZ( ) { return data()->flags & RW_HAS_DEFAULT_AWZ; } #define RW_HAS_DEFAULT_AWZ (1<<16) |
RW_HAS_DEFAULT_AWZ 這個是用來標示當前的class或者是superclass是否有預設的alloc/allocWithZone:。值得注意的是,這個值會儲存在metaclass 中。
hasDefaultAWZ( )方法是用來判斷當前class是否有預設的allocWithZone。
如果cls->ISA()->hasCustomAWZ()返回YES,意味著有預設的allocWithZone方法,那麼就直接對class進行allocWithZone,申請記憶體空間。
1 |
if (allocWithZone) return [cls allocWithZone:nil]; |
allocWithZone會去呼叫rootAllocWithZone
1 2 3 |
+ (id)allocWithZone:(struct _NSZone *)zone { return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone); } |
接下來就仔細看看_objc_rootAllocWithZone的具體實現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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 || UseGC) { obj = class_createInstance(cls, 0); } else { obj = class_createInstanceFromZone(cls, 0, zone); } #endif if (!obj) obj = callBadAllocHandler(cls); return obj; } |
在__OBJC2__中,直接呼叫class_createInstance(cls, 0);方法去建立物件。
1 2 3 4 |
id class_createInstance(Class cls, size_t extraBytes) { return _class_createInstanceFromZone(cls, extraBytes, nil); } |
關於_class_createInstanceFromZone方法這裡先不詳細分析,下面再詳細分析,先理清程式脈絡。
在objc的老版本中要先去看看zone是否有空間,是否用了垃圾回收,如果沒有空間,或者用了垃圾回收,就會呼叫class_createInstance(cls, 0)方法獲取物件,否則呼叫class_createInstanceFromZone(cls, 0, zone);獲取物件。
1 2 3 4 |
id class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone) { return _class_createInstanceFromZone(cls, extraBytes, zone); } |
可以看到,建立物件最終呼叫的函式都是_class_createInstanceFromZone,不管objc的版本是新版還是舊版。
如果建立成功就返回objc,如果建立失敗,就會呼叫callBadAllocHandler方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
static id callBadAllocHandler(Class cls) { // fixme add re-entrancy protection in case allocation fails inside handler return (*badAllocHandler)(cls); } static id(*badAllocHandler)(Class) = &defaultBadAllocHandler; static id defaultBadAllocHandler(Class cls) { _objc_fatal("attempt to allocate object of class '%s' failed", cls->nameForLogging()); } |
建立物件失敗後,最終會呼叫_objc_fatal輸出”attempt to allocate object of class failed”建立物件失敗。
到此就完成了callAlloc中hasCustomAWZ( )返回YES的情況。那麼hasCustomAWZ( )函式返回NO,情況是怎麼樣的呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
if (! cls->ISA()->hasCustomAWZ()) { // 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 (cls->canAllocFast()) { // No ctors, raw isa, etc. Go straight to the metal. bool dtor = cls->hasCxxDtor(); id obj = (id)calloc(1, cls->bits.fastInstanceSize()); if (!obj) return callBadAllocHandler(cls); obj->initInstanceIsa(cls, dtor); return obj; } else { // Has ctor or raw isa or something. Use the slower path. id obj = class_createInstance(cls, 0); if (!obj) return callBadAllocHandler(cls); return obj; } } |
這一段是hasCustomAWZ( )返回NO的情況,對應的是當前class沒有預設的allocWithZone的情況。
在沒有預設的allocWithZone的情況下,還需要再次判斷當前的class是否支援快速alloc。如果可以,直接呼叫calloc函式,申請1塊bits.fastInstanceSize()大小的記憶體空間,如果建立失敗,也會呼叫callBadAllocHandler函式。
如果建立成功,就去初始化Isa指標和dtor。
1 2 3 4 5 6 |
bool hasCxxDtor() { return data()->flags & RW_HAS_CXX_DTOR; } // class or superclass has .cxx_destruct implementation #define RW_HAS_CXX_DTOR (1<<17) |
dtor是用來判斷當前class或者superclass是否有.cxx_destruct函式的實現。
如果當前的class不支援快速alloc,那麼就乖乖的去呼叫class_createInstance(cls, 0);方法去建立一個新的物件。
小結一下:
經過上面的一系列判斷,“孕育物件”的過程最終落在了_class_createInstanceFromZone函式上了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
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(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocIndexed(); size_t size = cls->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (!UseGC && !zone && fast) { obj = (id)calloc(1, size); if (!obj) return nil; obj->initInstanceIsa(cls, hasCxxDtor); } else { #if SUPPORT_GC if (UseGC) { obj = (id)auto_zone_allocate_object(gc_zone, size, AUTO_OBJECT_SCANNED, 0, 1); } else #endif if (zone) { obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size); } else { obj = (id)calloc(1, size); } if (!obj) return nil; // Use non-indexed isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); } if (cxxConstruct && hasCxxCtor) { obj = _objc_constructOrFree(obj, cls); } return obj; } |
ctor 和 dtor 分別是什麼呢?
1 2 3 4 5 6 7 8 9 10 11 |
bool hasCxxCtor() { // addSubclass() propagates this flag from the superclass. assert(isRealized()); return bits.hasCxxCtor(); } bool hasCxxCtor() { return data()->flags & RW_HAS_CXX_CTOR; } #define RW_HAS_CXX_CTOR (1<<18) |
ctor是判斷當前class或者superclass 是否有.cxx_construct構造方法的實現。
1 2 3 4 5 6 7 8 9 10 11 |
bool hasCxxDtor() { // addSubclass() propagates this flag from the superclass. assert(isRealized()); return bits.hasCxxDtor(); } bool hasCxxDtor() { return data()->flags & RW_HAS_CXX_DTOR; } #define RW_HAS_CXX_DTOR (1<<17) |
dtor是判斷判斷當前class或者superclass 是否有.cxx_destruct析構方法的實現。
1 2 3 4 5 |
size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size ro->instanceSize; } |
例項大小 instanceSize會儲存在類的 isa_t結構體中,然後經過對齊最後返回。
注意:Core Foundation 需要所有的物件的大小都必須大於或等於 16 位元組。
在獲取物件大小之後,直接呼叫calloc函式就可以為物件分配記憶體空間了。
關於calloc函式
The calloc( ) function contiguously allocates enough space for count objects that are size bytes of memory each and returns a pointer to the allocated memory. The allocated memory is filled with bytes of value zero.
這個函式也是為什麼我們申請出來的物件,初始值是0或者nil的原因。因為這個calloc( )函式會預設的把申請出來的空間初始化為0或者nil。
申請完記憶體空間之後,還需要再初始化Isa指標。
1 2 3 |
obj->initInstanceIsa(cls, hasCxxDtor); obj->initIsa(cls); |
初始化Isa指標有這上面兩個函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { assert(!UseGC); assert(!cls->requiresRawIsa()); assert(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor); } inline void objc_object::initIsa(Class cls) { initIsa(cls, false, false); } |
從上述原始碼中,我們也能看出,最終都是呼叫了initIsa函式,只不過入參不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) { assert(!isTaggedPointer()); if (!indexed) { isa.cls = cls; } else { assert(!DisableIndexedIsa); isa.bits = ISA_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.indexed is part of ISA_MAGIC_VALUE isa.has_cxx_dtor = hasCxxDtor; isa.shiftcls = (uintptr_t)cls >> 3; } } |
初始化的過程就是對isa_t結構體初始化的過程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL struct { uintptr_t indexed : 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; # define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18) }; |
具體初始化的過程請參見這篇神經病院Objective-C Runtime入院第一天——isa和Class
將當前地址右移三位的主要原因是用於將 Class 指標中無用的後三位清除減小記憶體的消耗,因為類的指標要按照位元組(8 bits)對齊記憶體,其指標後三位都是沒有意義的 0。
絕大多數機器的架構都是 byte-addressable 的,但是物件的記憶體地址必須對齊到位元組的倍數,這樣可以提高程式碼執行的效能,在 iPhone5s 中虛擬地址為 33 位,所以用於對齊的最後三位位元為 000,我們只會用其中的 30 位來表示物件的地址。
至此,孕育物件的過程就完成了。
二.物件的出生
一旦當我們呼叫init方法的時候,物件就會“出生”了。
1 2 3 |
- (id)init { return _objc_rootInit(self); } |
init會呼叫_objc_rootInit方法。
1 2 3 4 5 6 |
id _objc_rootInit(id obj) { // In practice, it will be hard to rely on this function. // Many classes do not properly chain -init calls. return obj; } |
而_objc_rootInit方法的作用也僅僅就是返回了當前物件而已。
三.物件的生長
關於物件的生長,其實是想談談物件初始化之後,訪問它的屬性和方法,它們在記憶體中的樣子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#import @interface Student : NSObject @property (strong , nonatomic) NSString *name; +(void)study; -(void)run; @end #import "Student.h" @implementation Student +(void)study { NSLog(@"Study"); } -(void)run { NSLog(@"Run"); } @end |
這裡我們新建一個Student類,來舉例說明。這個類很簡單,只有一個name的屬性,加上一個類方法,和一個例項方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Student *stu = [[Student alloc]init]; NSLog(@"Student's class is %@", [stu class]); NSLog(@"Student's meta class is %@", object_getClass([stu class])); NSLog(@"Student's meta class's superclass is %@", object_getClass(object_getClass([stu class]))); Class currentClass = [Student class]; for (int i = 1; i < 5; i++) { NSLog(@"Following the isa pointer %d times gives %p %@", i, currentClass,currentClass); currentClass = object_getClass(currentClass); } NSLog(@"NSObject's class is %p", [NSObject class]); NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class])); |
寫出上述的程式碼,分析一下結構。
輸出如下:
1 2 3 4 5 6 7 8 9 |
Student's class is Student Student's meta class is Student Student's meta class's superclass is NSObject Following the isa pointer 1 times gives 0x100004d90 Student Following the isa pointer 2 times gives 0x100004d68 Student Following the isa pointer 3 times gives 0x7fffba0b20f0 NSObject Following the isa pointer 4 times gives 0x7fffba0b20f0 NSObject NSObject's class is 0x7fffba0b2140 NSObject's meta class is 0x7fffba0b20f0 |
經過上面的列印結果,我們可以知道,一個類的例項的isa是指向它的class,如下圖:
一個類的例項,虛線指向灰色的區域,灰色的區域是一個Class pair,裡面包含兩個東西,一個是類,另一個是meta-class。類的isa指向meta-class。由於student是繼承NSObject,所以Student的class的meta-class的superclass是NSObject。
為了弄清楚這3個東西里面分別存了些什麼,我們進一步的列印一些資訊。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
+ (NSArray *)instanceVariables { unsigned int outCount; Ivar *ivars = class_copyIvarList([self class], &outCount); NSMutableArray *result = [NSMutableArray array]; for (unsigned int i = 0; i < outCount; i++) { NSString *type = [NSString decodeType:ivar_getTypeEncoding(ivars[i])]; NSString *name = [NSString stringWithCString:ivar_getName(ivars[i]) encoding:NSUTF8StringEncoding]; NSString *ivarDescription = [NSString stringWithFormat:@"%@ %@", type, name]; [result addObject:ivarDescription]; } free(ivars); return result.count ? [result copy] : nil; } |
從之前的列印資訊我們能知道,0x100004d90是類的地址。0x100004d68是meta-class類的地址。
1 2 |
po [0x100004d90 instanceVariables] po [0x100004d68 instanceVariables] |
列印出來:
1 2 3 4 5 |
( NSString* _name ) nil |
從這裡就知道了,屬性這些是儲存在類中。
接下來就是關於類方法和例項方法的認識,+號方法和-號方法的認識。
在記憶體中其實沒有+號和-號方法的概念。做個試驗:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
+ (NSArray *)ClassMethodNames { NSMutableArray * array = [NSMutableArray array]; unsigned int methodCount = 0; Method * methodList = class_copyMethodList([self class], &methodCount); unsigned int i; for(i = 0; i < methodCount; i++) { [array addObject: NSStringFromSelector(method_getName(methodList[i]))]; } free(methodList); return array; } |
1 2 |
po [0x100004d90 ClassMethodNames] po [0x100004d68 ClassMethodNames] |
列印出來:
1 2 3 4 5 6 7 8 9 10 |
( .cxx_destruct, name, setName:, run ) ( study ) |
0x100004d90是類物件,裡面儲存的是-號方法,還有另外3個方法,getter,setter,還有.cxx_destruct方法
0x100004d68是meta-class,裡面儲存的是+號方法。
當然在runtime的meta-class有一處很特殊,那就是NSObject的meta-class,它的superclass是它自己本身。為了防止呼叫NSObject協議裡面的減號方法可能會出現崩潰,比如copy的-號方法,於是在NSObject的meta-class裡面把所有的NSObject的+號方法都重新實現了一遍,就是為了訊息傳遞到這裡,攔截了一遍。所以一般NSObject協議方法同一個方法都有+號和-號方法。
值得說明的是,class和meta-class都是單例。
關於物件,所有的物件在記憶體裡面都有一個isa,isa就是一個小“雷達”,有了它,就可以在runtime下給一個物件傳送訊息了。
所以物件的實質:Objc中的物件是一個指向ClassObject地址的變數,即 id obj = &ClassObject 。
關於物件的屬性實質是,void *ivar = &obj + offset(N)
1 2 3 4 5 6 7 8 9 10 |
NSString *myName = @"halfrost"; NSLog(@"myName 地址 = %p , 大小 = %lu ",&myName ,sizeof(myName)); id cls = [Student class]; NSLog(@"Student class = %@ 地址 = %p , 大小 = %lu", cls, &cls,sizeof(cls)); void *obj = &cls; NSLog(@"Void *obj = %@ 地址 = %p , 大小 = %lu", obj,&obj, sizeof(obj)); NSLog(@"%@ %p",((__bridge Student *)obj).name,((__bridge Student *)obj).name); |
輸出
1 2 3 4 |
myName 地址 = 0x7fff562eeaa8 , 大小 = 8 Student class = Student 地址 = 0x7fff562eeaa0 , 大小 = 8 Void *obj = 地址 = 0x7fff562eea98 , 大小 = 8 halfrost 0x10a25c068 |
從這個例子就可以說明,物件的實質就是指向類物件的地址變數,從上面例子裡面obj就可以看出, id obj = &ClassObject ,cls是Student的類物件,所以obj是Student的物件。
類物件是在main函式執行之前就載入進記憶體的,可執行檔案中和動態庫所有的符號(Class,Protocol,Selector,IMP,…)都已經按格式成功載入到記憶體中,被 runtime 所管理,再這之後,runtime 的那些方法(動態新增 Class、swizzle 等等才能生效)
具體可以看這篇文章iOS 程式 main 函式之前發生了什麼
還是回到例子中來,關於物件的屬性,就是obj的地址加上偏移量,就可以訪問到,上述的例子中,obj地址是0x7fff562eea98,往下偏移8,到了class的地址,0x7fff562eeaa0,再往下偏移8,就到了name屬性的地址,0x7fff562eeaa8。在name中儲存的是字串的首地址,根據列印資訊也看到了,儲存的是一個指標,指向的0x10a25c068的地址。
如果我們列印一下這個地址:
就會發現裡面存的就是我們的字串。
總結一下就是上面這張圖,每個物件的isa都存的是Class的記憶體地址,Class是在main函式執行之前就載入進記憶體的,並且由Runtime所管理。所以只需要構造一個指向Class的指標,即isa,就可以成為一個物件。
而物件的屬性,就是在物件的首地址上進行的偏移。如上圖,當知道物件的首地址是0x7fff562eea98,那麼偏移8個位元組就到了isa,再偏移8個位元組就到了name屬性了。物件的屬性就是在記憶體中偏移定址取值的過程。
四.物件的銷燬
物件的銷燬就是呼叫dealloc方法。
1 2 3 |
- (void)dealloc { _objc_rootDealloc(self); } |
dealloc方法會呼叫_objc_rootDealloc方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
void _objc_rootDealloc(id obj) { assert(obj); obj->rootDealloc(); } inline void objc_object::rootDealloc() { assert(!UseGC); if (isTaggedPointer()) return; if (isa.indexed && !isa.weakly_referenced && !isa.has_assoc && !isa.has_cxx_dtor && !isa.has_sidetable_rc) { assert(!sidetable_present()); free(this); } else { object_dispose((id)this); } } |
如果是TaggedPointer,直接return。
indexed是代表是否開啟isa指標優化。weakly_referenced代表物件被指向或者曾經指向一個 ARC 的弱變數。has_assoc代表物件含有或者曾經含有關聯引用。has_cxx_dtor之前提到過了,是析構器。has_sidetable_rc判斷該物件的引用計數是否過大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
id object_dispose(id obj) { if (!obj) return nil; objc_destructInstance(obj); #if SUPPORT_GC if (UseGC) { auto_zone_retain(gc_zone, obj); // gc free expects rc==1 } #endif free(obj); return nil; } |
object_dispose會呼叫objc_destructInstance。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/*********************************************************************** * objc_destructInstance * Destroys an instance without freeing memory. * Calls C++ destructors. * Calls ARR ivar cleanup. * Removes associative references. * Returns `obj`. Does nothing if `obj` is nil. * Be warned that GC DOES NOT CALL THIS. If you edit this, also edit finalize. * CoreFoundation and other clients do call this under GC. **********************************************************************/ void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = !UseGC && obj->hasAssociatedObjects(); bool dealloc = !UseGC; // This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); if (dealloc) obj->clearDeallocating(); } return obj; } |
銷燬一個物件,靠的是底層的C++解構函式完成的。還需要移除associative的引用。
接下來就依次詳細看看銷燬物件的3個方法。
1.object_cxxDestruct
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
void object_cxxDestruct(id obj) { if (!obj) return; if (obj->isTaggedPointer()) return; object_cxxDestructFromClass(obj, obj->ISA()); } static void object_cxxDestructFromClass(id obj, Class cls) { void (*dtor)(id); // Call cls's dtor first, then superclasses's dtors. for ( ; cls; cls = cls->superclass) { if (!cls->hasCxxDtor()) return; dtor = (void(*)(id)) lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct); if (dtor != (void(*)(id))_objc_msgForward_impcache) { if (PrintCxxCtors) { _objc_inform("CXX: calling C++ destructors for class %s", cls->nameForLogging()); } (*dtor)(obj); } } } |
從子類開始沿著繼承鏈一直找到父類,向上搜尋SEL_cxx_destruct
這個selector,找到函式實現(void (*)(id)(函式指標)並執行。
以下引用ARC下dealloc過程及.cxx_destruct的探究的內容:
從這篇文章中:
ARC actually creates a -.cxx_destruct method to handle freeing instance variables. This method was originally created for calling C++ destructors automatically when an object was destroyed.
和《Effective Objective-C 2.0》中提到的:
When the compiler saw that an object contained C++ objects, it would generate a method called .cxx_destruct. ARC piggybacks on this method and emits the required cleanup code within it.
可以瞭解到,.cxx_destruct方法原本是為了C++物件析構的,ARC借用了這個方法插入程式碼實現了自動記憶體釋放的工作。
在ARC中dealloc方法在最後一次release後被呼叫,但此時例項變數(Ivars)並未釋放,父類的dealloc的方法將在子類dealloc方法返回後自動呼叫。ARC下物件的例項變數在根類[NSObject dealloc]中釋放(通常root class都是NSObject),變數釋放順序各種不確定(一個類內的不確定,子類和父類間也不確定,也就是說不用care釋放順序)
經過@sunnyxx文中的研究:
1.ARC下物件的成員變數於編譯器插入的.cxx_desctruct方法自動釋放。
2.ARC下[super dealloc]方法也由編譯器自動插入。
至於.cxx_destruct方法的實現,還請看@sunnyxx 那篇文章裡面詳細的分析。
2._object_remove_assocations
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void _object_remove_assocations(id object) { vector > elements; { AssociationsManager manager; AssociationsHashMap &associations(manager.associations()); if (associations.size() == 0) return; disguised_ptr_t disguised_object = DISGUISE(object); AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { // copy all of the associations that need to be removed. ObjectAssociationMap *refs = i->second; for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) { elements.push_back(j->second); } // remove the secondary table. delete refs; associations.erase(i); } } // the calls to releaseValue() happen outside of the lock. for_each(elements.begin(), elements.end(), ReleaseValue()); } |
在移除關聯物件object的時候,會先去判斷object的isa_t中的第二位has_assoc的值,當object 存在並且object->hasAssociatedObjects( )值為1的時候,才會去呼叫_object_remove_assocations方法。
_object_remove_assocations方法的目的是刪除第二張ObjcAssociationMap表,即刪除所有的關聯物件。刪除第二張表,就需要在第一張AssociationsHashMap表中遍歷查詢。這裡會把第二張ObjcAssociationMap表中所有的ObjcAssociation物件都存到一個陣列elements裡面,然後呼叫associations.erase( )刪除第二張表。最後再遍歷elements陣列,把ObjcAssociation物件依次釋放。
這裡移除的方式和Associated Object關聯物件裡面的remove方法是完全一樣的。
3.clearDeallocating( )
1 2 3 4 5 6 7 8 9 10 11 12 13 |
inline void objc_object::clearDeallocating() { if (!isa.indexed) { // Slow path for raw pointer isa. sidetable_clearDeallocating(); } else if (isa.weakly_referenced || isa.has_sidetable_rc) { // Slow path for non-pointer isa with weak refs and/or side table data. clearDeallocating_slow(); } assert(!sidetable_present()); } |
這裡涉及到了2個clear函式,接下來一個個的看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void objc_object::sidetable_clearDeallocating() { SideTable& table = SideTables()[this]; // clear any weak table items // clear extra retain count and deallocating bit // (fixme warn or abort if extra retain count == 0 ?) table.lock(); RefcountMap::iterator it = table.refcnts.find(this); if (it != table.refcnts.end()) { if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) { weak_clear_no_lock(&table.weak_table, (id)this); } table.refcnts.erase(it); } table.unlock(); } |
遍歷SideTable,迴圈呼叫weak_clear_no_lock函式。
weakly_referenced代表物件被指向或者曾經指向一個 ARC 的弱變數。has_sidetable_rc判斷該物件的引用計數是否過大。如果其中有一個為YES,則呼叫clearDeallocating_slow()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Slow path of clearDeallocating() // for objects with indexed isa // that were ever weakly referenced // or whose retain count ever overflowed to the side table. NEVER_INLINE void objc_object::clearDeallocating_slow() { assert(isa.indexed && (isa.weakly_referenced || isa.has_sidetable_rc)); SideTable& table = SideTables()[this]; table.lock(); if (isa.weakly_referenced) { weak_clear_no_lock(&table.weak_table, (id)this); } if (isa.has_sidetable_rc) { table.refcnts.erase(this); } table.unlock(); } |
clearDeallocating_slow也會最終呼叫weak_clear_no_lock方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/** * Called by dealloc; nils out all weak pointers that point to the * provided object so that they can no longer be used. * * @param weak_table * @param referent The object being deallocated. */ void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) { objc_object *referent = (objc_object *)referent_id; weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); if (entry == nil) { /// XXX shouldn't happen, but does with mismatched CF/objc //printf("XXX no entry for clear deallocating %p\n", referent); return; } // zero out references weak_referrer_t *referrers; size_t count; if (entry->out_of_line) { referrers = entry->referrers; count = TABLE_SIZE(entry); } else { referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; } for (size_t i = 0; i |
這個函式會在weak_table中,清空引用計數表並清除弱引用表,將所有weak引用指nil。
總結
這篇文章詳細的分析了objc物件 從 出生 到 最終銷燬,它的今生今世全部在此。還請大家多多指點。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式