retain
或 release
代碼。引用計數就像辦公室的燈的照明c++
對照明設備所作的動做 | 對OC對象所作的動做 |
---|---|
開燈 | 生成對象 |
須要照明 | 持有對象 |
不須要照明 | 釋放對象 |
關燈 | 廢棄對象 |
其中,A生成對象時,引用計數爲 1, 當多一我的須要照明,如B須要照明,則引用計數 +1, 以此類推。當A不須要對象,A釋放對象,引用計數 -1.當最後一個持有對象的人都不要這個對象了,則引用計數變爲 0,丟棄對象。objective-c
客觀正確的思考方式:macos
對象操做 | OC方法 |
---|---|
生成並持有對象 | alloc/new/copy/mutableCopy等 |
持有對象 | retain |
釋放對象 | release |
廢棄對象 | dealloc |
本身生成的對象,本身所持有:持有對象編程
- (id) allocObject {
// 本身生成並持有對象
id obj = [[NSObject alloc] init];
return obj;
}
複製代碼
須要注意的是: NSMutableArray 類的 array 方法取得的對象不是本身所持有的。其內部實現原理爲:數組
- (id)object {
// 本身生成並持有對象
id obj = [[NSObject alloc] init];
// 將對象註冊到 autoreleasepool 中, pool結束時會自動調用 release,這樣的方法本身就不會持有對象。
[obj autorelease];
// 返回這個本身不持有的對象。
return obj;
}
複製代碼
非本身生成的對象,本身也能持有:雖然一開始是不持有的,可是可使用 retain 使其變成被本身所持有的,而後也可使用 release 方法釋放對象。緩存
// 取得非本身生成的對象
id obj = [NSMutableArray array];
// 取得的對象存在了,可是並不是本身所持有的,引用計數還爲 0, 可是該對象被放到了autoreleasepool 中,能夠自動釋放
[obj retain];
// 此時,本身就持有了這個對象,引用計數爲 1
[obj release];
// 此時釋放了這個對象,引用計數變爲 0 ,對象就不能夠再被訪問了,可是對象也沒有被當即廢棄
複製代碼
沒法釋放非本身持有的對象:例如安全
// 取得非本身持有的對象
id obj = [NSMutableArray array];
[obj release];
// 會致使程序崩潰
複製代碼
分析 GNU 源碼來理解 NSObject 類中的方法。多線程
首先是 alloc id obj = [[NSObject alloc] init];
框架
+ (id)alloc {
// alloc 在內部調用 allocWithZone
return [self allocWithZone:NSDefaultMallocZone()];
}
+ (id)allocWithZone:(NSZone *)zone {
// allocWithZone 在內部調用 NSAllocateObject
return NSAllocateObject(self, 0, z);
}
struct obj_layout {
NSUInteger retained;
};
inline id NSAllocateObject (Class aClass, NSUInteger extreBytes, NSZone *zone) {
int size = 計算容納對象所需內存的大小;
// 分配內存空間
id new = NSZoneMalloc(zone, size);
// 將該內存空間中的值初始化爲 0
memset(new, 0, size);
// 返回做爲對象而使用的指針
new = (id)&((struct obj_layout *) new)[1];
}
/** 其中, NSZoneMalloc, NSDefaultMallocZone() 等名稱中包含的 Zone 是爲了防止內存碎片化而引入的結構。對內存分配的區域自己進行多重化管理,根據對象使用的目的,大小,分配內存,從而提升內存管理的效率。 可是如今的運行時系統知識簡單的忽略了區域的概念,運行時系統中的內存管理自己已經機具效率,再使用區域來管理內存反而會引發內存使用效率低下的問題。 */
複製代碼
去掉NSZone後簡化的代碼函數
struct obj_layout {
NSUInteger retained;
};
+ (id)alloc {
int size = sizeof(struct obj_layout) + 對象大小;
// 這句的意思是,爲 struct obj_layout 這個結構體分配一個 size 大小的內存空間,而且函數calloc()會將所分配的內存空間中的每一位都初始化爲零,也就是這塊內存中全部的值都爲 0
struct obj_layout *p = (struct obj_layout *)calloc(1, size);
// 返回該對象指針
return (id)(p + 1);
}
複製代碼
[obj retain];
的實現
- (id)retain {
NSIncrementExtraRefCount(self);
}
inline void NSIncrementExtraRefCount(id anObject) {
// 首先 (struct obj_layout *) anObject 找到的是這個對象的尾部, 因此須要 [-1] 減去該對象的大小,來尋址到該對象的頭部,而後再判斷該結構體中 retained 這個變量的值是否已經大於了系統最大值,若是沒有,就 retained++, 使得引用計數 +1.
if (((struct obj_layout *) anObject)[-1].retained == UINT_MAX - 1) {
[NSException raise: NSInternalInconsistencyException format:@"NSIncrementExtraRefCount() asked to increment too far"];
((struct obj_layout *) anObject) [-1].retained++;
}
}
複製代碼
[obj release]
的實現
- (void)release {
if (NSDecrementExtraRefCountWasZero(self)) {
[self delloc];
}
}
BOOL NSDecrementExtraRefCountWasZero(id anObject) {
if (((struct obj_layout *) anObject)[-1].retained == 0) {
return YES;
} else {
((struct obj_layout *) anObject)[-1].retained--;
return NO;
}
}
複製代碼
[obj dealloc];
的實現
- (void)dealloc {
NSDeallocateObject(self);
}
inLine void NSDeallocateObject (id anObject) {
// 指針 o 指向 anObject 的內存地址,而後釋放這個指針指向的內存
struct obj_layout *o = &((struct obj_layout *) anObject) [-1];
free(o);
}
複製代碼
首先看 alloc 的實現:
// 依次調用這四個方法
+ alloc
+ allocWithZone:
class_Instance
calloc
複製代碼
retainCount / retain / release 的實現
- retainCount
__CFDoExtrernRefOperation
CFBaseicHashGetCountOfKey
- retain
__CFDoExternRefOperation
CFBasicHashAddValue;
- release
__CFDoExternRefOperation
CFBasicHashRemoveValue
// 這些函數的前綴 CF 表示他們都包含於 Core Foundation 框架的源代碼中
複製代碼
因此其內部實現可能以下:
int __CFDoExternRefOperation(uintptr_r op, id obj) {
CFBasicHashRef table = 取得對象的散列表(obj);
int count;
switch (op) {
case OPERATION_retainCount:
count = CFBasicHashGetCountOfKey(table, obj);
return count;
case OPERATION_retain:
CFBasicHashAddValue(table, obj);
return obj;
case OPERATION_release:
count = CFBasicHashRmoveValue(table, obj);
// 若是count == 0, 返回 YES, 則會調用 dealloc
return 0 == count;
}
}
// 舉例說明 retainCount
- (NSUInteger)retainCount {
return (NSUInteger)__CFDExternRefOperation(OPERATION_retainCount, self);
}
複製代碼
由此可看出,蘋果在計數內部大概是以散列表的方式來管理引用計數的。複習散列表
比較
經過內存塊頭部管理引用計數的好處:
少許代碼便可完成
可以統一管理引用計數須要的內存塊和對象所用的內存塊
經過引用計數表管理引用計數的好處
autorelease 會像 C語言 的自動變量同樣來對待對象實例。當其超出做用域時,就會對對象進行release 的調用。
autorelease 的具體使用方法:
生成並持有 NSAutoreleasePool 對象
調用已經分配對象的 autorelease 實例方法
廢棄 NSAutoreleasePool 對象(對對象自動調用 release)
// 代碼以下
NSAutoreleasePool pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; => 等價於 [obj release];
複製代碼
咱們在編程中,並不須要顯式的調用 pool 對象,由於在 RunLoop 中,這一切都爲咱們處理好了。在一個 RunLoop 循環中,會進行 NSAutoreleasePool 對象的生成,應用程序的主線程進行處理,廢棄 NSAutoreleasePool 對象。
儘管是這樣,咱們有的時候也須要顯式的調用 NSAutoreleasePool 對象,由於有時會產生大量的 autorelease 對象,只要不廢棄 NSAutoreleasePool 對象,那麼這些生成的對象就不能被釋放,會致使內存瘋長的現象。最典型的例子就是在讀取大量圖像的同時改變它的尺寸。
另外,在 Cocoa 框架中也有不少類方法用於返回 autorelease 對象。好比
id array = [NSMutableArray arrayWithCapasity:1];
// 等價於
id array = [[[NSMuatbleArray alloc] initWithCapasity:1] autorelease];
複製代碼
首先來看 GNU 的源代碼
首先看一下 autorelease 方法的實現
[obj autorelease];
// 表面上的實現方法
- (id)autorelease {
[NSAutoreleasePool addObject:self];
}
/**
實際上, autorelease 內部是用 Runtime 的 IMP Caching 方法實現的。在進行方法調用時,爲了解決類名/方法名幾區的方法運行是的函數指針,要在框架初始化時對他們進行緩存
*/
id autorelease_class = [NSAutoreleasePool class];
SEL autorelease_sel = @selector(addObject:);
IMP autorelease_imp = [autorelease_class methodForSelector:autorelease_sel];
// 實際的方法調用時使用緩存的結果值
- (id)autorelease {
(*autorelease_imp)(autorelease_class, autorelease_sel, self);
}
複製代碼
再看 NSAutoreleasePool 的 addObject 類方法實現
+ (void)addObject:(id)obj {
NSAutoreleasePool *pool = 取得正在使用的 NSAutoreleasePool 對象;
if (pool) {
[pool addObject:anObj];
} else {
NSLog("不存在正在使用的 NSAutoreleasePool 對象");
}
}
複製代碼
addObject 實例方法實現
// 當調用 NSObject類的 autorelease 實例方法時,這個對象就會被加到 NSAutoreleasePool 對象數組中
- (void)addObject:(id)obj {
[array addObject:obj];
}
複製代碼
drain 實例方法廢棄正在使用的 NSAutoreleasePool 對象的過程
// 執行順序: drain() -> dealloc() -> emptyPool() -> [obj release] -> [emptyPool release]
- (void)drain {
[self dealloc];
}
- (void)dealloc {
[self emptyPool];
[array release];
}
- (void)emptyPool {
for (id obj in array) {
[obj release];
}
}
複製代碼
C++的實現
class AutoreleasePoolPage {
static inline void *push() {
// 生成或持有 NSAutoreleasePool 對象
}
static inline id autorelease(id obj) {
// 對應 NSAutoreleasePool 類的 addObject 類方法
AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的 AutoreleasePoolPage 實例;
autoreleasePoolPage -> add(obj);
}
static inline void *pop(void *token) {
// 廢棄 NSAutoreleasePool 對象
releaseAll();
}
id *add(id obj) {
// 添加對象到 AutoreleasePoolPage 的內部數組中
}
void releaseAll() {
// 調用內部數組對象的 release 類方法
}
};
// 具體調用
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void *objc_autoreleasePoolPop(void *ctxt) {
return AutoreleasePoolPage::push(ctxt);
}
id *objc_autorelease(void) {
return AutoreleasePoolPage::autorelease(obj);
}
複製代碼
觀察 NSAutoreleasePool 類方法和 autorelease 方法的運行過程
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// == objc_autoreleasePoolPush()
id obj = [[NSObject alloc] init];
[obj autorelease];
// == objc_autorelease(obj)
[pool drain];
// == objc_autoreleasePoolPop(pool);
複製代碼
另外:[[NSAutoreleasePool showPools]];
能夠用來確認已經被 autorelease 的對象的情況。
問題: 若是 autorelease NSAutoreleasePool
對象會如何?
ARC 有效時,id 類型和對象類型與 C語言 中的其餘類型不一樣,其類型必須附加 全部權修飾符 。共有如下四種
__strong 修飾符
__strong 修飾符是 id 類型和對象類型默認的全部權修飾符
// 在 ARC 有效的環境下
id obj = [[NSObject alloc] init] <==> id __strong obj = [[NSObject alloc] init];
// 在 ARC 無效的環境下
{
id obj = [[NSObject alloc] init]
[obj release];
}
複製代碼
當被__strong 修飾符修飾的,本身生成的,對象在超過其做用域時:
{
// 本身生成並持有對象
id __strong obj = [[NSObject alloc] init];
/**
由於變量 obj 爲強引用,因此本身持有對象
*/
}
// 由於變量超出做用域,強引用失效,因此釋放對象,由於對象此時沒有其餘的全部者了,對象被廢棄。
// 正好遵循內存管理的原則
複製代碼
當對象的全部者和對象的生命週期是明確的,取得非本身生成並持有的對象時:
{
// 首先取得非本身生成的對象,可是因爲__strong修飾符修飾着這個對象,因此本身持有這個對象
id __strong obj = [NSMutableArray array];
}
/**
當超出對象的做用域時,強引用失效,因此釋放對象。
可是因爲 [NSMutableArray array] 所生成的對象並不是本身所持有的,而是自動的加到 autoreleasePool 中,因此會在一個 RunLoop 週期結束後,自動廢棄對象。
*/
複製代碼
有 __strong修飾符的變量之間能夠相互賦值
// 首先 obj0 強引用指向 對象A , obj1 強引用指向 對象B,表示 obj1 持有 B, obj2 不持有任何對象
id __strong obj0 = [[NSObject alloc] init]; // 對象A
id __strong obj1 = [[NSObject alloc] init]; // 對象B
id __strong obj2 = nil;
// 此時 obj0 與 obj1 強引用同一個對象 B, 沒有人持有 對象A 了,因此 對象A 被廢棄。
obj0 = obj1;
// 此時 obj2 指向 obj0 所持有的對象, 因此 對象B 如今被三個引用所持有。
obj2 = obj0;
// 如今 obj1 對 對象B 的強引用失效,因此如今持有 對象B 的強引用變量爲 obj0,obj2
obj1 = nil;
// 同理,如今只有 obj2 持有對象B
obj0 = nil;
// 沒有引用指向 對象B 了,廢棄 對象B
obj2 = nil;
複製代碼
__strong修飾符 也能夠修飾 OC類成員變量,也能夠在方法的參數上,使用附有 _strong 修飾符的變量
@interface Test : NSObject
{
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
@implementation Test
- (instancetype)init
{
self = [super init];
return self;
}
- (void)setObject:(id)obj {
obj_ = obj;
}
@end
// 調用函數
{
// 首先test 持有 Test 對象的強引用
id __strong test = [[Test alloc] init];
// Test對象 的 obj_ 成員,持有 NSObject 對象的強引用
[test setObject:[[NSObject alloc] init]];
}
/**
此時test強引用超出了其做用域,它失效了。
因此此時沒有強引用指向 Test對象 了, Test對象會被廢棄
廢棄 Test 對象的同時, Test對象 的 obj_ 成員也被廢棄。
因此它釋放了指向 NSObject 的強引用
由於 NSObject 沒有其餘全部者了,因此 NSObject 對象也被廢棄。
*/
複製代碼
_strong修飾符 與 _weak, _autoreleasing 修飾符同樣,初始化時,即便不明確指出,他們也都會自動將該引用指向nil。經過 _strong修飾符,完美的知足了 引用計數的思考方式
id類型和對象類型的全部權修飾符默認都爲 __strong 因此不須要再顯式的指明修飾對象的修飾符爲 _strong
__weak 修飾符
_weak修飾符 的出現就是爲了解決 _strong修飾符在內存管理中所帶來的循環引用問題。如上例:
@interface Test : NSObject
{
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
@implementation Test
- (instancetype)init
{
self = [super init];
return self;
}
- (void)setObject:(id)obj {
obj_ = obj;
}
@end
// 調用函數,打印變量結果以下:
{
// 首先test1 持有 Test 對象的強引用, test2 持有 Test 對象的強引用
id __strong test1 = [[Test alloc] init]; // 對象A
id __strong test2 = [[Test alloc] init]; // 對象B
/**
TestA 對象中的 obj_ 成員變量持有着 test2指向的 對象B, 同時,test2指向的對象B中的 obj_又強引用着 對象A, 因此形成了循環引用。
*/
[test1 setObject:test2];
[test2 setObject:test1];
}
/**
當跳出做用域後,test1釋放它對 對象A 的強引用
test2釋放它對 對象B 的強引用
可是此時 對象A中的 obj_A 對 對象B 的強引用本應該被釋放,可是因爲在 對象B 中強引用了對象A,因此 obj_A 不會被釋放,會一直強引用 對象B, 而同理,對象B 中的 obj_B 也不會被釋放,因此它將一直強引用着 對象A, 因此此時外部沒有誰引用着 對象A 和 對象B, 可是他們本身在互相引用着,這樣就形成了內存泄漏!(所謂內存泄漏,指的就是應該被廢棄的對象,卻在超出其生存週期變量做用域時還繼續存在着)
*/
/**
打印變量結果以下:
其中 test1 對象中強引用 test2 對象, test2對象 又強引用 test1 對象,形成無盡的循環。
*/
複製代碼
而下面這種狀況:
{
id test = [[Test alloc] init];
[test setObject:test];
}
/**
當強引用test 的做用域結束後,它釋放了對 Test 對象的引用。
可是 Test對象 內部還保留着 對 Test對象 的強引用,因此 Test對象 被引用着,因此不會被回收
*/
// 也會發生內存泄漏!
複製代碼
因此此時,就很是須要一個 __weak修飾符 來避免循環引用
// 弱引用與強引用正好相反,不可以持有對象實例。
// 這樣寫會發出警告:Assigning retained object to weak variable; object will be released after assignment
// 表示由於沒有人持有着 NSObject 對象,因此該對象一旦被建立就會當即被銷燬
id __weak obj = [[NSObject alloc] init];
// 正確的使用弱引用的方式
{
// 本身生成並持有 NSObject 對象
id obj = [[NSObject alloc] init];
// 由於 NSObject 對象已經被 obj 強引用着, 因此此時 obj1 對它使用弱引用也沒有關係,
// 不會使它的引用計數 +1
id __weak obj1 = obj;
}
/**
當超出變量的做用域時, obj 對 NSObject對象 的強引用消失,
此時沒有人持有 NSObject對象 了。
NSObject對象 被廢棄
*/
複製代碼
對上述循環引用的例子進行修改以下:
@interface Test : NSObject
{
id __weak obj_;
}
- (void)setObject:(id __strong)obj;
@end
@implementation Test
- (instancetype)init
{
self = [super init];
return self;
}
- (void)setObject:(id)obj {
obj_ = obj;
}
@end
// 調用函數,打印變量結果以下:
{
// 首先test1 持有 Test 對象的強引用, test2 持有 Test 對象的強引用
id __strong test1 = [[Test alloc] init]; // 對象A
id __strong test2 = [[Test alloc] init]; // 對象B
/**
TestA 對象中的 obj_ 成員變量弱引用着 test2指向的 對象B, 同時,test2指向的 對象B 中的 obj_又弱引用着 對象A。
*/
[test1 setObject:test2];
[test2 setObject:test1];
}
/**
當跳出做用域後,test1釋放它對 對象A 的強引用
test2釋放它對 對象B 的強引用
此時,因爲 對象中的 obj_變量只擁有對對象的弱引用,因此 沒有誰持有着 對象A,和對象B,他們被釋放,沒有形成循環引用!
*/
複製代碼
__weak修飾符 的另外一優勢:當持有某個對象的弱引用時,若是該對象被廢棄,則弱引用將自動失效,而且會被置爲 nil的狀態(空弱引用)
id __weak obj1 = nil;
{
// 本身生成並持有對象
id __strong obj0 = [[NSObject alloc] init];
// obj1 如今也指向 NSObject對象
obj1 = obj0;
// 此時打印 obj1 有值
NSLog(@"A = %@", obj1);
}
/**
當變量 obj0 超出做用域,它再也不持有 NSObject對象,
因爲 obj1 是弱引用,因此它也不持有 NSObject對象
因爲沒人持有 NSObject對象, NSObject對象被廢棄
被廢棄的同時, obj1 變量的弱引用失效, obj1 被從新賦值爲 nil
*/
NSLog(@"B = %@", obj1);
/**
結果打印以下:
2017-12-14 15:16:39.859875+0800 littleTest[10071:1377629] A = <NSObject: 0x10054da70>
2017-12-14 15:16:39.860432+0800 littleTest[10071:1377629] B = (null)
*/
複製代碼
__unsafe_unretained 修飾符
_unsafe_unretained 修飾符 是不安全的修飾符,在 iOS4 之前用來代替 _weak修飾符
id __unsafe__unretained obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
obj1 = obj0;
NSLog(@"A = %@", obj1);
}
NSLog(@"B = %@", obj1);
/**
該源碼沒法正確執行,由於 __unsafe_unretained修飾符 使變量既不強引用對象,也不弱引用對象。
當 obj0 超出做用域時, NSObject 無引用,因此被釋放
在此同時, obj1 有時會錯誤訪問對象,造成下面這種打印
2017-12-14 15:38:21.462724+0800 littleTest[10140:1399554] A = <NSObject: 0x10044eea0>
2017-12-14 15:38:21.463007+0800 littleTest[10140:1399554] B = <NSObject: 0x10044eea0>
有時會發生錯誤,直接使程序崩潰。
形成這兩種狀況的本質爲: obj1 訪問的對象已經被廢棄了,形成了 垂懸指針!
*/
複製代碼
因此,須要注意的是,當使用 _unsafe_unretained 修飾符 訪問對象時,必需要確保該對象確實是真實存在的。
__autoreleasing 修飾符
在 ARC 有效時,咱們不可使用以下代碼,由於這些代碼是在非 ARC 環境下使用的:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
複製代碼
做爲替換,在 ARC 有效時, 咱們會使用
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
複製代碼
他們的對應關係如圖:
咱們能夠非顯式的使用 __autoreleasing 修飾符 。
狀況一:當取得非本身生成並持有的對象時,雖然可使用 alloc/new/copy/mutableCopy 之外的方法來取得對象,可是該對象已經被註冊到 autoreleasePool 中。這和在 ARC無效 時,調用 autorelease 方法取得的結果相同。這是由於編譯器會自動檢查方法名是否以alloc/new/copy/mutableCopy 開始,若是不是,則自動將對象註冊到 autoreleasePool 中。
@autoreleasepool {
// 首先取得非本身生成的對象,由於 obj 的強引用,因此它持有這個對象
// 由於這個對象的方法名不是以 alloc/new/copy/mutableCopy 開頭的,因此他被自動註冊到 autoreleasePool中了。
{
id __strong obj = [NSMutableArray array];
}
/**
當變量超出其做用域時,他失去對這個對象的強引用。因此它會釋放本身所持有的對象
可是此時 autoreleasePool 中還持有着對這個對象的引用,因此它不會當即被廢棄
*/
}
/**
當 autoreleasePool 的做用域也結束後,沒有人持有這個對象了,因此它被廢棄了。
*/
複製代碼
驗證上述說法,首先:建立對象的方式爲 [NSMutableArray array]
:
// 在 autoreleasepool 的做用域外定義一個 obj1 持有弱引用
id __weak obj1 = nil;
@autoreleasepool {
{
id __strong obj = [NSMutableArray array];
obj1 = obj;
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
/**
打印結果:
2017-12-14 16:32:07.118513+0800 littleTest[10242:1444479] (
)
2017-12-14 16:32:07.118819+0800 littleTest[10242:1444479] (
)
2017-12-14 16:32:07.118850+0800 littleTest[10242:1444479] (null)
結果代表:當obj0超出其做用域時,它失去了對對象的引用。可是因爲該對象被自動註冊到 autoreleasepool 中,使得第二個 NSLog 打印時 obj1 依舊弱引用着這個對象,當第三個 NSLog 打印時,因爲 autoreleasepool 已經被清空,因此這個對象也被銷燬了, obj1 又被重置爲 nil
*/
複製代碼
此時,建立對象的方式爲:[[NSMutableArray alloc] init]
id __weak obj1 = nil;
@autoreleasepool {
{
id __strong obj = [[NSMutableArray alloc] init];
obj1 = obj;
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
/**
打印結果以下:
2017-12-14 16:36:09.584864+0800 littleTest[10257:1449554] (
)
2017-12-14 16:36:09.585131+0800 littleTest[10257:1449554] (null)
2017-12-14 16:36:09.585149+0800 littleTest[10257:1449554] (null)
這是由於,使用 alloc/new/copy/mutableCopy 方法建立對象時,不會將該對象自動的放入 autoreleasePool 中,這就使得當 obj0 超出其做用域後,就沒有人強引用着 NSMutableArray 對象了,該對象也就被廢棄了。
*/
複製代碼
如下爲 取得非本身生成並持有的對象 時所調用方法:
+ (id)array {
return [[NSMutableArray alloc] init];
}
/**
這段代碼也沒有使用 __autorelease修飾符,因此這個方法內部的對象不會被註冊到 autoreleasePool 中。
*/
// 上述方法也能夠寫成以下形式:
+ (id)array {
id obj = [[NSMutableArray alloc] init];
return obj;
}
/**
由於 return 使得 obj 超出做用域,因此它所指向的對象 NSMutableArray 會被自動釋放,可是由於 return 將這個對象做爲函數的返回值返回給主調函數,因此這個對象不會被廢棄。而且因爲這個對象的生成方法是將其做爲返回值,不是由alloc/new/copy/mutableCopy 方法建立的,因此 NSMutableArray 對象會被自動添加到 autoreleasePool 中
*/
複製代碼
狀況二: 在訪問有 __weak修飾符 的變量時,實際上一定會訪問註冊到 autoreleasePool 中的對象
id __weak obj1 = obj0;
NSLog(@"class=%@", [obj1 class]);
// 等價於
id __weak obj1 = obj0;
id _autoreleasing temp = obj1;
NSLog(@"class=%@", [temp class]);
/**
出現這種狀況的緣由是由於:__weak修飾符 只持有對象的弱引用,這樣無法保證它訪問對象的過程當中,對象不被廢棄。因此咱們將他要訪問的對象放到 autoreleasePool 中,這樣就會使得 @autoreleasePool塊 結束以前都能保證該對象的存在。
*/
複製代碼
狀況三:因爲 id obj <==> id __strong obj
因此咱們但願能推出 id *obj <==> id __strong *obj
可是實際上並不是如此,實際狀況是 id *obj <==> id __autoreleasing obj
同理:NSObject **obj <==> NSObject * __autoreleasing *obj
,像這樣的,id 的指針或對象的指針在沒有顯示的指定時,會被附加上 __autoreleasing修飾符
// 例如 NSString 中的這個方法
stringWithContentsOfFile:(nonnull NSString *) encoding:(NSStringEncoding) error:(NSError * _Nullable __autoreleasing * _Nullable)
// 使用這個方式的源代碼以下:
NSError *error = nil;
BOOL result = [obj performOperationWithError:&error];
// 函數聲明以下
- (BOOL)performOperationWithError:(NSError **)error;
// 等價於
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
/**
之因此使用 __autoreleasing 做爲修飾符,是由於咱們這個方法聲明的參數的是 error 這個指針變量的指針,也就是 須要傳遞 error 的地址。而在這個方法內部的執行以下,它改變了 error 這個指針所指向的內容。使其指向了一個由 alloc 生成的對象。而咱們須要明白的內存管理的思考方式爲:除了由 alloc/new/copy/mutableCopy 生成的對象外,其餘方式生成的對象都必須要註冊到 autoreleasePool 中。並取得非本身所持有的對象。因此將變量聲明爲 (NSError * __autoreleasing *)error 就能夠實現這一目的。
*/
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error {
*error = [[NSError alloc] initWithDomain:MyAppDomain code:errorCode userInfo:nil];
return NO;
}
/**
可能對於不熟悉 C語言 的小夥伴來講,不是很明白爲何這裏非要將函數參數聲明爲 「指針的指針」,這是由於當咱們僅僅把參數聲明爲指針時,方法就變爲以下,當咱們給函數傳遞指針時,默認會生成跟指針類型相同的實例變量。當咱們在這個方法中操做指針時,咱們覺得操做的是指針,實際上只是這複製的實例變量。也就是說,在這個例子中 error 就是這個複製的實例變量。當這個方法結束時,error 會被釋放,其所指向的內容也會一併被釋放。因此此時外部的 error 依舊指向 nil。沒有任何改變。
而當咱們使用 (NSError * __autoreleasing *)error 做爲參數時,雖然複製的實例變量狀況仍是存在,可是此次複製的是「指針的指針」,也就是說,它指向跟參數指針相同指針地址, 在函數內部使用 *error 獲取到了指針地址,使其指向了 NSError對象 。這樣,雖然函數當出了其做用域時,那個複製的實例變量被銷燬了,可是它改變了函數外部 error 指針所指向的對象,使其從 nil 變成了 NSError對象。
*/
- (BOOL)performOperationWithError:(NSError *)error {
error = [[NSError alloc] initWithDomain:MyAppDomain code:errorCode userInfo:nil];
return NO;
}
複製代碼
對於函數傳遞指針及指針的指針 還不明白的請看這裏
👇的代碼會產生編譯錯誤:
NSError *error = nil;
NSError **perror = &error;
//Pointer to non-const type 'NSError *' with no explicit ownership
複製代碼
由於
// 須要改變perror的全部權修飾符
NSError *error = nil;
NSError *__strong *perror = &error;
// 對於其餘類型的全部權修飾符也同樣
NSError __weak *error = nil;
NSError *__weak *perror = &error;
/**
但是咱們剛剛在調用
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error
方法時,並無將 NSError *error 轉化爲 __autoreleasing修飾符修飾的,這是爲何?
*/
// 實際上,編譯器自動幫咱們轉化了修飾符
NSError *error = nil;
NSError *_autoreleasing *temp = nil;
BOOL result = [obj performOperationWithError:&temp];
error = temp;
複製代碼
像 NSAutoreleasePool 同樣, autoreleasepool 也能夠嵌套使用。例如在 iOS 程序中,整個程序都被包含在 @autoreleasepool塊 中。
NSRunLoop等實現不管 ARC 是否有效,都可以隨時釋放註冊到 autoreleasepool 中的對象。
在 ARC 有效時編譯代碼,必須遵照如下規則:
不能使用 retain/release/retainCount/autorelease
不能使用 NSAllocateObject/NSDeallocateObject
必須遵照內存管理方法命名規則
在 ARC 無效時,用於對象生成/持有須要遵循以下規則:alloc/new/copy/mutableCopy。以上述名稱開始的方法在返回對象時,必須得返回給調用方應當持有的對象。這點在 ARC 有效時也是同樣。
在 ARC 有效時,追加的一條命名規則:init
以 init 開始的方法規則要比 alloc/new/copy/mutableCopy 更加嚴格。該方法必須得是實例方法。不容許是類方法。而且返回對象應該爲 id 類型或者該方法聲明類的對象類型,或者是該類的超類或子類。該返回對象並不註冊到 autoreleasepool 中。基本上只是對 alloc 方法返回值的對象進行初始化操做並返回該對象。
// 如下爲使用該方法的源代碼: init 方法會初始化alloc 方法返回值,而且返回該對象
id obj = [[NSObject alloc] init];
// 下列是不容許的,由於它沒有返回對象
- (void)initTheData:(id)data;
// 另外,👇方法雖然也有init, 但它不包含在命名規則裏,由於他是一個單詞 initialize
- (void)initialize;
複製代碼
不能顯示的調用 dealloc
[super dealloc];
使用 @autoreleasepool塊 代替 NSAutoreleasePool
不能使用區域 NSZone
對象型變量不能做爲 C語言 結構體的成員
C語言 的結構體中若是存在 OC對象型變量 會引發編譯錯誤
若是非要將對象加入結構體,則可強制轉化爲 void * 或者附加 _unsafe_unretained修飾符 ,由於被 _unsafe_unretained修飾符所修飾的對象,已經不屬於編譯器的內存管理對象了。
struct Data {
NSMutableArray __unsafe__unretained *array
}
複製代碼
顯示的轉換 id 和 void *
當 ARC 無效時,將 id變量 強制轉化爲 void *變量 不會出現問題
id obj = [[NSObject alloc] init];
void *p = obj;
// 將 void * 賦給 id變量 中,調用他的實例方法,運行時也不會出現問題
id o = p;
[o release];
複製代碼
當 ARC 有效時,會引發編譯錯誤。此時,id型 或 對象型變量 賦值給 void * 或者逆向賦值時都須要進行特定的轉換,若是隻是想單純的賦值則可使用 bridge轉換
id obj = [[NSObject alloc] init];
// id轉化爲 void *,它的安全性比 __unsafe__unretained 還要低,一不當心就會有垂懸指針
void *p = (__bridge void *)obj;
// void * 轉換爲 id
id o = (__bridge id)p;
複製代碼
__bridge轉換 中還包括 _bridge_retained轉換, _bridge_transfer轉換
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj
複製代碼
該代碼在 非ARC 環境下
id obj = [[NSObject alloc] init];
void *p = obj;
// __bridge__retained轉變爲了 retain,使得 p 和 obj 都持有了這個對象
[(id)p retain];
複製代碼
一個其餘的例子:
void *p = 0;
{
id obj = [[NSObject alloc] init];
p = (__bridge_retained void *)obj;
}
NSLog(@"class = %@", [(__bridge_retained)p class]);
複製代碼
該代碼在 非ARC 環境下
void *p = 0;
{
id obj = [[NSObject alloc] init];
p = [obj retain];
[obj release];
}
/**
此時 p 依舊持有對 NSObject對象 的引用
*/
NSLog(@"class = %@", [(__bridge_retained)p class]);
複製代碼
__bridge_transfer,被轉換的變量所持有的對象在該變量被賦值給轉換目標變量後釋放
id obj = (__bridge_transfer id)p;
複製代碼
非ARC 環境下
id obj = (id)p;
[obj retain];
[(id)p release];
複製代碼
Objective-C 對象 與 Foundation對象
屬性聲明的屬性 與 全部權修飾符 對應的關係
屬性聲明的屬性 | 全部權修飾符 |
---|---|
assign | _unsafe_unretained |
copy | __strong(賦值的是被複制的對象) |
retain | __strong |
strong | __strong |
unsafe_unretained | _unsafe_unretained |
weak | __weak |
靜態數組的狀況:
// 將附有各類修飾符的變量做爲靜態數組的使用狀況
// 好比
id __weak obj[10];
// 除了 __unsafe__unretained修飾符以外的其餘修飾符都是會將數組元素的值默認初始化爲nil
// 當數組超出其變量做用域時,內存管理也一樣適用於他之中的各個對象
複製代碼
動態數組:在這種狀況下,根據不一樣的目的選擇使用 NSMutableArray, NSMutableDictionary, NSMutableSet 等 Foundation 框架中的容器,這些容器會恰當的持有追加的對象並會爲咱們管理這些對象。
看一下動態數組在 C語言 中的實現
// 首先,聲明一個動態數組須要使用指針。來表示指針的地址
id __strong *array = nil;
//這裏是因爲 id * 類型的指針默認修飾符爲 id __autoreleasing * 類型, 因此有必要顯示的指定爲 __strong 修飾符。另外,雖然保證了附有 __strong修飾符 的 id 類型變量被初始化爲 nil, 可是不保證 array變量, 也就是 id指針型變量 被初始化爲 nil
// 當類型是其餘類型時,以下:
NSObject * __strong *array = nil;
// 以後,使用 calloc函數 確保想分配的,附有 __strong修飾符變量 的容量佔有的內存塊
array = (id __strong *)calloc(entries, sizeof(id));
// 其中 entries 表示內存塊的數量。而且 calloc 函數將數組中的每一個變量指向的對象都自動初始化爲 nil
// 注意這裏若是使用了 malloc函數 來分配內存, 則須要手動的將每一個變量所指向的對象都初始化爲 0,注意這裏只能使用 memset等函數 來進行初始化賦值
// 而後,經過 calloc函數 分配的動態數組就能徹底按照靜態數組的方法使用
array[0] = [[NSObject alloc] init];
// 可是在動態數組中操做 __strong修飾符 的變量與靜態數組有很大差別,須要本身手動釋放數組,可是當它釋放時,必須手動的先將數組的每一個變量都置爲nil,此時不能使用 memset等函數 將數組中的元素值設爲 0 。這也會內存泄漏
for (NSInteger i = 0; i < entries; ++i) {
array[i] = nil;
}
free(array);
複製代碼
觀察賦值給附有 __strong修飾符 的變量在實際程序中究竟是如何運行的,👇代碼(首先是正常的會使引用計數 +1 的 alloc/new/copy/mutableCopy 方法):
{
id __strong obj = [[NSObject alloc] init];
}
複製代碼
該段代碼轉化爲彙編代碼後,爲(具體如何轉化爲彙編代碼,請看個人另外一篇文章):
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 13
.globl _main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## BB#0:
pushq %rbp
Lcfi0:
.cfi_def_cfa_offset 16
Lcfi1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Lcfi2:
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl $0, -4(%rbp)
movq L_OBJC_CLASSLIST_REFERENCES_$_(%rip), %rax
movq L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq %rax, %rdi
callq _objc_msgSend
leaq -16(%rbp), %rdi
xorl %ecx, %ecx
movl %ecx, %esi
movq %rax, -16(%rbp)
callq _objc_storeStrong
xorl %eax, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
.section __DATA,__objc_classrefs,regular,no_dead_strip
.p2align 3 ## @"OBJC_CLASSLIST_REFERENCES_$_"
L_OBJC_CLASSLIST_REFERENCES_$_:
.quad _OBJC_CLASS_$_NSObject
.section __TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_: ## @OBJC_METH_VAR_NAME_
.asciz "alloc"
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
.p2align 3 ## @OBJC_SELECTOR_REFERENCES_
L_OBJC_SELECTOR_REFERENCES_:
.quad L_OBJC_METH_VAR_NAME_
.section __TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_.1: ## @OBJC_METH_VAR_NAME_.1
.asciz "init"
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
.p2align 3 ## @OBJC_SELECTOR_REFERENCES_.2
L_OBJC_SELECTOR_REFERENCES_.2:
.quad L_OBJC_METH_VAR_NAME_.1
.section __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
.long 0
.long 64
.subsections_via_symbols
複製代碼
簡化後的模擬代碼爲:
// 首先發送消息給 NSObject 類,消息內容爲 alloc 指令,而後將結果賦值給 obj
id obj = objc_msgSend(NSObject, @selector(alloc));
// 而後將 init 消息發送給 obj
objc_msgSend(obj, @selector(init));
// 最後釋放 obj
objc_release(obj);
// 由此可知,在 ARC 有效時,自動插入了 release 方法
複製代碼
取得 非本身生成的,可是本身持有的 對象:
id __strong obj = [NSMutableArray array];
複製代碼
轉化成彙編語言:
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 13
.globl _main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## BB#0:
pushq %rbp
Lcfi0:
.cfi_def_cfa_offset 16
Lcfi1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Lcfi2:
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl $0, -4(%rbp)
movq L_OBJC_CLASSLIST_REFERENCES_$_(%rip), %rax
movq L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq %rax, %rdi
callq _objc_msgSend
movq %rax, %rdi
callq _objc_retainAutoreleasedReturnValue
leaq -16(%rbp), %rdi
xorl %ecx, %ecx
movl %ecx, %esi
movq %rax, -16(%rbp)
callq _objc_storeStrong
xorl %eax, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
.section __DATA,__objc_classrefs,regular,no_dead_strip
.p2align 3 ## @"OBJC_CLASSLIST_REFERENCES_$_"
L_OBJC_CLASSLIST_REFERENCES_$_:
.quad _OBJC_CLASS_$_NSMutableArray
.section __TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_: ## @OBJC_METH_VAR_NAME_
.asciz "array"
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
.p2align 3 ## @OBJC_SELECTOR_REFERENCES_
L_OBJC_SELECTOR_REFERENCES_:
.quad L_OBJC_METH_VAR_NAME_
.section __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
.long 0
.long 64
.subsections_via_symbols
複製代碼
簡化後的彙編語言:
// 首先發送 array 消息給接收者 NSMutableArray, 而後將結果的返回值賦給 obj
id obj = objc_msgSend(NSMutableArray, @selector(array));
/**
obj_retainAutoreleasedReturnValue 函數主要用於最優化程序運行,顧名思義 obj_retainAutoreleasedReturnValue 表示的是 「持有 Autorelease 的返回值」,表示的是,它是用於本身持有對象的函數,但他持有的對象應爲返回註冊在 autoreleasepool 中對象的方法,,或是函數的返回值。像這段源代碼同樣,也就是 obj 須要被 __strong 所修飾在調用 alloc/new/copy/mutableCopy 之外的方法時,由編譯器插入該函數
*/
objc_retainAutoreleasedReturnValue(obj);
// 釋放 obj
objc_release(obj);
複製代碼
因爲,objc_retainAutoreleasedReturnValue 函數老是成對出現的,因此實際上它還有一個姐妹:objc_autoreleaseReturnValue, 它主要用在 alloc/new/copy/mutableCopy 之外的方法生成對象時的返回對象上,也就是如👇所示
+ (id)array {
return [[NSMutableArray alloc] init];
}
// 轉化成彙編後的簡化代碼
+ (id)array {
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj, @selector(init));
// 此時,返回註冊到 autoreleasepool 中對象的方法:使用了 obj_autoreleaseReturnValue 函數來返回註冊到 autoreleasepool 中的對象,可是 obj_autoreleaseReturnValue 方法與 obj_autorelease 方法不一樣,通常不只限於註冊對象到 autoreleasepool 中
return objc_autoreleaseReturnValue(obj);
}
/**
objc_autoreleaseReturnValue 方法會檢查使用該函數的方法或調用方的執行命令列表,
1.若是方法或函數的調用方在調用了方法或函數後緊接着調用了 objc_retainAutoreleasedReturnValue() 函數,那麼就不會將返回的對象註冊到 autoreleasepool 中,而是直接傳遞到方法或函數的調用方去。
2.若是方法或函數的調用方在調用了方法或函數後緊接着沒調用objc_retainAutoreleasedReturnValue() 函數,那麼就會將返回對象註冊到 autoreleasepool 中。
而 objc_retainAutoreleasedReturnValue() 函數與 objc_retain 函數不一樣,他即使不註冊到 autoreleasepool 中,也能正確的獲取對象。
經過 objc_autoreleaseReturnValue 和 objc_retainAutoreleasedReturnValue 方法的協做,能夠不將對象註冊到 autoreleasepool 中二直接傳遞,這一過程達到最優化
*/
複製代碼
下面咱們來看看 __weak修飾符 原理實現:
{
id __weak obj1 = obj;
}
/**
編譯器的模擬代碼
*/
id obj1;
// 首先經過 obj_initWeak 函數初始化附有 __weak 修飾符的變量
objc_initWeak(&obj1, obj);
// 而後在變量做用域結束時,經過 obj_destroyWeak 函數釋放該變量
objc_destroyWeak(&obj1);
/**
其中,objc_initWeak 函數的做用是:將附有 __weak修飾符 的變量初始化爲 0 後,會將賦值的對象做爲參數調用 objc_storeWeak 函數
obj_destroyWeak 函數的做用是:將 0 做爲參數調用 obj_storeWeak 函數
*/
objc_initWeak(&obj1, obj); <==> obj1 = 0; objc_storeWeak(&obj1, obj);
objc_destroyWeak(&obj1) <==> objc_storeWeak(&obj1, 0);
/**
objc_storeWeak 函數把 第二個參數 的賦值對象的 地址 做爲 "鍵值",將 第一個參數 的附有 __weak修飾符 的變量的"地址"註冊到 weak 表 中。若是第二個參數爲 0 ,則把變量的地址從 weak 表中刪除
weak 表與引用計數表相同,實現方式都爲"散列表"。若是使用 weak 表,將廢棄對象的地址做爲鍵值進行搜索,就能高速的獲取對應的附有 weak修飾符 的變量的地址。另外,因爲一個對象能夠同時賦值給多個附有 weak修飾符 的變量中,因此對於一個鍵值,可註冊多個變量的地址。
*/
複製代碼
釋放對象時,廢棄沒人持有的對象的同時,程序是如何操做的,下面咱們來跟蹤觀察,對象將經過 objc_release 方法釋放
根據以上步驟可知:__weak修飾符 所修飾的變量所引用的對象被廢棄,該變量被置爲 nil 獲得實現。可是由此可知,若是大量使用附有 _weak修飾符修飾變量,將會產生性能問題。
在使用 __weak修飾符 時, 若是以下方式,會引發警告
id __weak obj = [[NSObject alloc] init];
// 由於該對象剛被建立就會被釋放
// 編譯器的模擬代碼
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
// 雖然本身生成並持有的對象經過 objc_initWeak 函數被賦值給附有 __weak修飾符 的變量中,可是編譯器判斷它沒有持有者,因此該對象當即經過 objc_release 方法釋放
objc_initWeak(&obj, tmp);
objc_release(tmp);
objc_destroyWeak(&obj);
// 這樣一來, nil 就會被賦值給引用廢棄對象的附有 __weak修飾符 的變量
複製代碼
關於當即釋放對象的一些思考
// 已知如下代碼會引發編譯器的警告,這是由於編譯器判斷生成並持有的對象不能繼續持有,由於沒有強引用指向它
id __weak obj = [[NSObject alloc] init];
// ---------------------------------------------------------------------------------
// 附有 __unsafe_unretained 修飾符的變量會怎樣? 也會產生警告
id __unsafe_unretained obj = [[NSObject alloc] init];
// 轉換成編譯器的模擬代碼:
id obj = obj_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
// obj_release 函數馬上釋放了生成並持有的對象,這樣該對象的垂懸指針被賦給 obj
objc_release(obj);
// ---------------------------------------------------------------------------------
// 若是在生成對象的時候不把它賦給變量會怎樣?
// 在 非ARC 環境下,必然會發生內存泄漏
// 可是在 ARC 環境下,因爲不能繼續持有該對象,會當即調用 obj_release 函數,因爲 ARC 的處理,這樣的代碼不會產生內存泄漏
[[NSObject alloc] init];
// ARC 下生成的代碼
id tmp = obj_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_release(tmp);
// ---------------------------------------------------------------------------------
// 是否能夠調用被當即釋放掉的對象的實例方法?
(void)[[[NSObject alloc] init] hash];
// 該代碼會變成以下形式:
id tmp = obj_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_msgSend(tmp, @selector(hash));
objc_release(tmp);
// 因此,obj_release 方法是在該對象實例方法調用完成後纔會被調用,因此能夠調用被當即釋放的對象的實例方法
複製代碼
看👇代碼
{
id __weak obj1 = obj;
NSLog(@"%@", obj1);
}
// 該源碼可轉化爲以下形式
id obj1;
objc_initWeak(&obj1, obj);
// objc_loadWeakRetained 取出附有 __weak修飾符 變量所引用的對象並 retain
id tmp = objc_loadWeakRetained(&obj1);
// 將對象註冊到 autoreleasepool 中
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&obj1);
複製代碼
由此可知:由於附有 __weak修飾符 的變量所引用的對象像這樣被註冊到 autoreleasepool 中,因此在 @autoreleasepool 塊結束以前均可以放心的使用 _weak修飾的變量。可是,不能大量的使用附有 _weak修飾符 修飾的變量,不然會引發註冊到 autoreleasepool 中的對象大大增長,所以在使用附有 _weak修飾符 的變量時,最好先暫時賦給附有 _strong修飾符 的變量後再使用。若看不太懂則能夠只看下面代碼:
// 下面這段代碼會使變量 o 所賦值的對象被註冊到 autoreleasepool 中 5 次
{
id __weak o = obj;
for (int i = 0; i < 5; ++i) {
NSLog(@"%d -- %@", i, o);
}
}
// 而下面這段代碼只會使變量 o 所賦值的對象被註冊到 autoreleasepool 中 1 次
{
id __weak o = obj;
id tmp = o;
for (int i = 0; i < 5; ++i) {
NSLog(@"%d -- %@", i, tmp);
}
}
複製代碼
allocWeakReference/retainWeakReference
實例方法返回 NO 的狀況。(這種狀況沒有被寫入 NSObject 類的接口說明文檔中),也就是說,這兩個方法咱們通常不會接觸到。將對象賦值給附有 __autoreleasing修飾符 的變量等同於 ARC 無效時,調用對象的 autorelease 方法。
首先看一下使用 alloc/new/copy/mutableCopy 時的狀況
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
// 模擬代碼
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasPoolPop(pool);
複製代碼
再看一下使用 alloc/new/copy/mutableCopy 之外的方法時的狀況
@autoreleasepool {
id __autoreleasing obj = [NSMutableArray array];
}
// 模擬代碼
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasPoolPop(pool);
// 雖然 obj 持有對象的方法變爲 objc_retainAutoreleasedReturnValue, 可是將 obj 所引用的對象註冊到 autoreleasepool 中的方法並無改變
複製代碼
引用計數數值自己究竟是什麼?
// 這個函數爲得到引用計數的函數數值
uintptr_t _objc_rootRetainCount(id obj);
{
id __strong obj = [[NSObject alloc] init];
NSLog(@"retain count = %d", _objc_rootRetainCount);
}
// 打印結果:retain count = 1
複製代碼
咱們實際上並不能徹底信任 _objc_rootRetainCount 這個函數所取得的數值,由於有時對於已經釋放的對象以及不正確的對象地址,有時也會返回 1 。 而且在多線程中使用對象的引用計數數值,由於有競爭狀態的問題,因此取得的數值並不必定徹底可信