自動引用計數

来源:http://www.cnblogs.com/chenjiangxiaoyu/archive/2017/10/24/7724752.html
-Advertisement-
Play Games

1.什麼是自動引用計數? 顧明思義,自動引用計數(ARC,Automatic Reference Counting)是指記憶體管理中對引用採取自動計數的技術。 在OC中採用ARC機制,讓編譯器來進行記憶體管理。在新一代apple LLVM編譯器中設置ARC為有效狀態,就無需再次鍵入retain或者rel ...


1.什麼是自動引用計數?

顧明思義,自動引用計數(ARC,Automatic Reference Counting)是指記憶體管理中對引用採取自動計數的技術。

在OC中採用ARC機制,讓編譯器來進行記憶體管理。在新一代apple LLVM編譯器中設置ARC為有效狀態,就無需再次鍵入retain或者release代碼,這在降低程式崩潰、記憶體泄露等風險的同時,很大程度上減少了開發程式的工作量。編譯器完全清楚目標對象,並能立刻釋放那些不在被使用的對象。

1.2 記憶體管理/引用計數

1.2.1 概要

OC中的記憶體管理,也就是引用計數。可以用開關房間的等為例來說明引用計數的機制,比如:上班進入辦公室需要照明,下班離開辦公室不需要照明。

假設辦公室里的照明設備只有一個。上班進入辦公室的人需要照明,所以要把燈打開,而對於下班離開辦公室的人來說,已經不需要照明瞭,所以要把燈關掉。若是很多人上下班,每個人都開燈或是關燈,那麼辦公室的情況又將如何呢?最早下班離開的人如果關了燈,辦公室里還沒有走的人都將處於一片黑暗當中。

解決這個問題的辦法是使辦公室在還有至少1人的情況下保持開燈狀態,而在無人時保持關燈狀態。

a.第一個人進入辦公室,“需要照明的人數”加1,計數值從0變成了1,因此要開燈。

b.之後每當有人進入辦公室,"需要照明的人數"就加1,計數值從1變成2.

c.每當有人下班離開辦公室,"需要照明的人數"就減1。如計數值從2變成1.

d.最後一個人下班離開辦公室時,"需要照明的人數"減1,計數值從1變成了0,因此要關燈。

對辦公室照明設備所做的動作和對OC對象所做的動作的對比。

對照明設備所做的動作:開燈 - 需要照明 - 不需要照明 - 關燈

對OC對象所做的動作:  生成對象 - 持有對象 - 釋放對象 - 廢棄對象。

使用計數功能計算需要照明的人數,使辦公室的照明得到了很好的管理。同樣,使用引用計數功能,對象也就能夠得到很好的管理,這就是OC的記憶體管理。

1.2.2 記憶體管理的思考方式

自己生成的對象,自己所持有。

非自己生成的對象,自己也能持有。

不在需要自己持有的對象時釋放。

非自己持有的對象無法釋放。

//  對應的方法

對象操作:生成並持有對象 - 持有對象 - 釋放對象 - 廢棄對象

OC方法  :alloc/new/copy/mutablecopy - retain - release - dealloc

這些有關OC記憶體管理的方法,實際上不包括在該語言中,而是包含在cocoa框架中,用於ios開發。cocoa框架中Foundation框架類庫的NSObject類擔負記憶體管理的職責。OC記憶體管理中的alloc/retain/release/dealloc方法分別指代NSObject類的alloc類方法、retain實例方法、release實例方法和dealloc實例方法。

自己生成對象,自己所持有

使用alloc new copy mutableCopy 開頭的方法名意味著自己生成的對象只有自己持有。本文所說的“自己”固然對應前文提到的“對象的使用環境”,但將之理解為編程人員“自身”也是沒錯的。下麵寫出了自己生成並持有對象的代碼,為生成並持有對象,我們使用alloc 方法。

//    自己生成並持有對象
id obj = [[NSObject alloc] init];

//   使用NSObject類的alloc類方法就能自己生成並持有對象。指向生成並持有對象的指針被賦給變數obj,另外,使用如下new類方法也能生成並持有對象。
//  自己生成並持有對象
id obj = [NSObject new];

copy 方法利用基於NSCopying方法約定,由各類實現的copyWithZone,方法生成並持有對象的副本。與copy方法類似,mutableCopy方法利用基於NSMutableCopying方法約定,由各類實現的mutableCopyWithZone方法生成並持有對象的副本。兩者的區別在於,copy方法生成不可變更的對象,而mutableCopy方法生成可變更的對象。這類似於NSArray和NSMutableArray的差異。

allocMyObject newThatObject copyThis mutableCopyYourObject 方法也意味著自己生成並持有對象。

但是allocate newer copying mutableCopyed 並不屬於同一類別的方法。

非自己生成的對象,自己也能持有 

用上述項目之外的方法取得的對象,即用alloc/new/copy/mutableCopy 以外的方法取得的對象,因為非自己生成並持有,所以自己不是該對象的持有者。我們來使用NSMutableArray類的array類方法。

//    取得非自己生成並持有的對象
id obj = [NSMutableArray array];

//    取得的對象存在,但是自己不持有對象,源代碼中,NSMutableArray類對象被賦值給obj,但變數obj自己並不持有該對象,使用retain方法可以持有對象。

//    取得非自己生成並持有的對象,取得的對象雖在,但是自己並不持有
id obj = [NSMutableArray array];

//    自己持有對象,通過retain方法,非自己生成的對象跟用alloc/new/copy/mutableCopy方法生成並持有的對象一樣,成為了自己所持有的。
[obj retain]

不需要自己持有的對象時釋放

自己持有的對象,一旦不再需要,持有者有義務釋放該對象,釋放使用release方法。 

//    自己生成並持有對象
id obj = [[NSObject alloc] init];

//    釋放對象,指向對象的指針仍然被保留在變數obj中,貌似能夠訪問。但是對象一經釋放絕對不可以訪問。

如此,用alloc方法由自己生成並持有的對象就通過release方法釋放了,自己生成而非自己所持有的對象,若用retain方法變為自己持有,也同樣可以用release方法釋放。

//    取得非自己生成並持有的對象
id obj = [NSMutableArray array]

//    自己持有對象
[obj retain]

//    自己釋放對象,對象不可再被訪問
[obj release]

 如果要用某個方法生成對象,並將其返還給該方法的調用方,那麼他的源代碼又是什麼樣的呢?

//    如下代碼,原封不動地返回用alloc方法生成並持有的對象,就能讓調用方也持有該對象。allocObject 名稱符合命名規則,因此它與用alloc方法生成並持有對象的情況完全相同,所以使用allocObject方法也就意味著“自己生成並持有對象”

- (id)allocObject{
   //    自己生成並持有對象
   id obj = [[NSObject alloc] init];
   //     自己持有對象
   return obj;
}

//  調用[NSMutableArray array] 方法使取得的對象存在,但自己不持有對象,又是如何實現的?根據上文的命名規則,不能使用以alloc/new/copy/mutableCopy開頭的方法名,因此要使用object這個方法名。

- (id)object{
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    return obj;  
}

//    上例中,我們使用了autorelease方法,用該方法,可以使取得的對象存在,但自己不持有對象。

 autorelease 提供這樣的功能,使對象在超出指定的生存範圍時能夠自動並正確的釋放(調用release方法)下麵是release和autorelease的區別

無法釋放非自己持有的對象

對於非alloc/new/copy/mutableCopy方法生成並持有的對象,或是用retain方法持有的對象,由於持有者是自己,所以在不需要該對象時需要將其釋放。而由此之外所得到的對象絕對不能釋放。倘若在應用程式中釋放了非自己所持有的對象就會造成崩潰。例如自己生成並持有對象後,在釋放完不再徐亞ode對象之後再次釋放。

//    自己生成並持有對象
id obj = [[NSObject alloc] init];

//    對象已釋放
[obj release];

//    釋放之後再次釋放已非自己持有的對象,應用程式崩潰,崩潰情況,再度廢棄已經廢棄了的對象時崩潰,訪問已經廢棄的對象時崩潰。
----------------------------------------------------------------------------------------------------------

//    取得的對象存在,但自己不持有對象
id objq = [obj0 object]

//    釋放了非自己持有的對象,這肯定會導致應用程式崩潰

 如這些例子所示,釋放非自己持有的對象會造成程式崩潰。因此絕對不要去釋放非自己持有的對象。

1.2.3 alloc/retain/release/dealloc 實現

包含NSObject類的Foundation框架並沒有公開,不過,Foundation框架使用的Core Foundation框架的源代碼,以及通過調用NSObject類進行記憶體管理部分的源代碼是公開的。但是,沒有NSObject類的源代碼,就很難瞭解NSObject類的內部實現細節,所以,我們首先使用開源軟體GNUstep來說明。

GNUstep是Cocoa框架的互換框架,也就是說,GUNstep的源代碼雖不能說和Cocoa實現完全相同,但是從使用者角度來看,兩者的行為和實現方式是一樣的,或者說非常相似,

id obj = [NSObject alloc];

//    這裡調用了NSObject類的alloc類方法在NSObject.m源代碼中的實現如下。

GNUstep/modules/core/base/Source/NSObject.m alloc

+(id)alloc{
    return [self allocWithZone: NSDefaultMallocZone( )];
}

+(id)allocWithZone: (NSZone *)z{
    return NSAllocateObject (self, 0 , z);
}

//    通過allocWithZone類方法調用NSAllocateObject函數分配了對象,下麵我們看看NSAllocateObject函數。

GNUstep/modules/core/base/Source/NSObject.m  NSAllocateObject

struct obj_layout{
    NSUInteger retained;
}

inline id
NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone){
    int size = 計算容納對象所需記憶體大小
    id new = NSZoneMalloc(zone,size);
    memset(new, 0, size);
    new = (id)& ((struct obj_layout *)new)[1];  
}

//    NSAllocateObject 函數通過調用NSZoneMalloc函數來分配存放對象所需的記憶體空間,之後將該記憶體空間置為0,最後返回作為對象而使用的指針。

 註:NSDefaultMallocZone,NSZoneMalloc等名稱中包含的NSZone是什麼呢?它是為了防止記憶體碎片化而引入的結構,對記憶體分配的區域本身進行多重化管理,根據使用對象的目的、對象的大小分配記憶體,從而提高了記憶體管理的效率。

但是,如同蘋果官方文檔中所說,現在的運行時系統只是簡單地忽略的區域的概念,運行時系統中的記憶體管理本身已經極具效率,使用區域來管理記憶體反而會引起記憶體使用效率低下以及源代碼複雜化等問題。

下麵是去掉NSZone後簡化了的源代碼:

GNUstep/modules/core/base/Source/NSObject.m alloc 簡化版

struct obj_layout {
    NSUInteger retained;
}

+ (id) alloc{
    int size = sizeof (struct obj_layout) + 對象大小
    struct obj_layout * p = (struct obj_layout *) calloc (1,size);
    return (id) (p+1)
}

 alloc類方法用struct obj_layout中的retained整數來保存引用計數,並將其寫入對象記憶體頭部,該對象記憶體塊全部置0後返回。

對象的引用計數可通過retainCount實例方法取得

id obj = [[NSObject alloc] init];
NSLog(@"retainCount = %d",[objc retainCount]);

//    顯示retainCount = 1
執行alloc後對象的retainCount 是 “1”,下麵通過GNUstep的源代碼來確認。

- (NSUInteger) retainCount{
   return NSExtraRefCount (self) + 1;
}

inline NSUInteger
NSExtraRefCount (id anObject){
    return ((struct obj_layout *) anObject) [-1].retained;
}

1.2.4 蘋果的實現

在NSObject 類的alloc類方法上設置斷點,追蹤程式的執行,以下列出了執行所調用的方法和函數。

+alloc

+allocWithZone

class_createInstance

calloc

alloc類方法首先調用allocWithZone類方法,這和GNUstep的實現相同,然後調用class_createInstance函數,最後通過調用calloc來分配記憶體塊。這和前面的GNUstep的實現並無多大差異。

retainCount/retain/release實例方法又是怎麼實現的呢?下麵列出各個方法分別調用的方法阿和函數

-retainCount

_CFDoExternRefOperation

CFBasicHashGetCountOfKey

 

-retain

_CFDoexternRefOperation

CFBasicHashAddValue

 

-release 

_CFDoExternRefOperation

CFBasicHashRemoveValue

(CFBasicHashRemoveValue 返回0 時,-release調用dealloc)

各個方法都通過同一個調用了_CFDoExternRefOperation函數,調用了一系列名稱相似的函數。如這些函數名的首碼"CF"所示,它們包含於Core Foundation框架源代碼中,即是CFRuntime.c的_CFDoExternRefOperation函數。

1.2.5 autorelease

autorelease 就是自動釋放,這看上去很像ARC,但實際上它更類似於C語言中自動變數(局部變數)的特性。

在C語言中,程式執行時,如果某自動變數超出其作用域,該自動變數將被自動廢棄。

{
   int a;
}

//    因為超出變數的作用域,自動變數 int a將被廢棄,不可再訪問。

 

autorelease會像C語言的自動變數那樣來對待對象實例。當超出其作用域(相當於變數作用域)時,對象實例的release實例方法被調用。另外,同C語言的自動變數不同的是,編程人員可以設定變數的作用域。

autorelease的具體使用方法如下:

(1)生成並持有NSAutoreleasePool 對象。

(2)調用已分配對象的autorelease實例方法

(3)廢棄NSAutoreleasePool對象。

NSAutoreleasePool對象的生存周期相當於C語言變數的作用域。對於所有調用過autorelease實例方法的對象,在廢棄NSAutoreleasePool對象時,都將調用release實例方法。

源代碼如下:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]

//    這裡的[pool drain] 等同於 “[obj release]”

 在Cocoa框架中,相當於程式主迴圈的NSRunLoop或者在其它程式可運行的地方,對NSAutoreleasePool對象進行生成,持有和廢棄處理。因此,應用程式開發者不一定非得使用該對象進行開發工作。

儘管如此,但在大量產生autorelease的對象時,只要不廢棄NSAutoreleasePool對象,那麼生成的對象就不能被釋放,因此有時會產生記憶體不足的現象。典型的例子就是讀取大量圖像的同時改變其尺寸。圖像文件讀入到NSData對象,並從中生成UIImage對象,改變該對象尺寸後生成新的UIImage對象,這種情況下,就會大量產生autorelease的對象。

在此情況下有必要在適當的地方,生成,持有或廢棄Pool對象。

通常在使用OC,也就是Foundation框架時,無論調用哪一個對象的autorelease實例方法,實現上是調用的都是NSObject類的autorelease實例方法。但是對於NSAutoreleasePool類,atorelease實例方法已被該類重載,因此運行時就會出錯。

1.3 ARC規則

1.3.1 概要

實際上“引用計數式”記憶體管理的本質部分在ARC中並沒有改變,就像 “自動引用計數”這個名稱表示的那樣,ARC只是自動的幫助我們處理“引用計數”的相關部分。

對某個文件可選擇使用或不使用ARC.

1.3.2 記憶體管理的思考方式

自己生成的對象,自己持有

非自己生成的對象,自己也能持有

自己持有的對象不再需要時釋放

非自己持有的對象無法釋放

1.3.3 所有權修飾符

OC編程中為了處理對象,可將變數類型定義為id類型或各種對象類型。

所謂對象類型就是指向NSObject這樣的OC類的指針,如 “NSObject *”。id類型用於隱藏對象類型的類名部分,相當於C語言中常用的 “void *”

ARC有效時,id類型和對象類型通C語言其它類型不同,其類型上必須附加所有權修飾符。

__strong __weak __unsafe_unretained __autoreleasing

__strong 修飾符

__strong 修飾符是id類型和對象類型預設的所有權修飾符。也就是說,以下源代碼中的id變數,實際上被附加了所有權修飾符。

id obj = [[NSObject alloc] init];

id 和對象類型在沒有明確指定所有權修飾符時,預設為__strong修飾符。上面的代碼與以下相同。

id __strong obj = [[NSObject alloc] init];

該源代碼再ARC無效的時候又該如何表述呢?

//    ARC無效
id obj = [[NSObject alloc] init];

//    該源代碼一看則名,目前在錶面上沒有任何變化,在看看下麵的代碼
{
    id __strong obj = [[NSObject alloc] init];
}

此源代碼明確指定了C語言的變數的作用於。ARC無效的時候,該源代碼可記述如下:
//   ARC無效
{
    id obj = [[NSObject alloc] init];
    [obj release];
}

 為了釋放生成並持有的對象,增加了調用release方法的代碼。該源代碼進行的動作同先前ARC有效時的動作完全一樣。

如此源代碼所示,附有__strong修飾符的變數obj在超出其變數作用域時,即在該變數被廢棄時,會釋放其被賦予的對象。

如 "strong" 這個名稱所示,__strong 修飾符表示對對象的"強引用"。持有強引用的變數在超出其作用域時被廢棄,隨著強引用的失效,引用的對象會隨之釋放。

下麵關註一下源代碼中關於對象的所有者的部分

{
   id __strong obj = [[NSObject alloc] init];
}

//    此源代碼就是之前自己生成並持有對象的源代碼,該對象的所有者如下:

{
    //    自己生成並持有對象
   id __strong obj = [[NSObject alloc] init]
   
    //    因為變數obj為強引用,所以自己持有該對象
}

//    因為變數obj超出其作用域,強引用失效,所以自動地釋放自己持有的對象,對象的所有者不存在,因此廢棄該對象。

 此處,對象的所有者和對象的生存周期是明確的,那麼,在取得非自己生成並持有的對象時又會如何呢?

//    在NSMutableArray類的array類方法的源代碼中取得非自己生成並持有的對象,具體如下

{  
     //    取得非自己生成並持有的對象    
     id __strong obj = [NSMutableArray array];
    
     //     因為變數obj為強引用,所以自己持有對象
}

//    因為變數obj超出其作用域,強引用失效,所以自動地釋放自己持有的對象

在這裡對象的所有者和對象的生存周期也是明確的

{
    //    自己生成並持有對象
    id __strong obj = [[NSObject alloc] init];
    //    因為變數obj為強引用,所以自己持有對象
   
}

 //    因為變數obj超出其作用域,強引用失效,所以自動地釋放自己持有的對象,對象的所有者不存在,因此廢棄該對象。

 當然,附有__strong 修飾符的變數之間可以相互賦值。

id __strong obj0 = [[NSObject alloc] init]; // 對象A
//    obj0持有對象A的強引用

id __strong obj1 = [[NSObject alloc] init]; //   對象B
//    obj1持有對象B的強引用

id __strong obj2 = nil;
//    obj2不持有任何對象

obj0 = obj1;
//    obj0持有由obj1賦值的對象B的強引用,因為obj0被賦值,所以原先持有的對對象A的強引用時效,對象A的所有者不存在,因此廢棄對象A.
此時,持有對象B的強引用的變數為 obj0 和 obj1

obj2 = obj0;
//    obj2 持有obj0賦值的對象B的強引用,此時持有對象B的強引用的變數為obj0,obj1,obj2

obj1 = nil;
此時持有對象B的強引用的變數為obj0,obj2

obj2 = nil;
此時持有對象B的強引用的變數為obj0

obj3 = nil;
此時持有對象B的強引用的變數沒有,所以廢棄對象B

 __weak 修飾符

兩個對象迴圈引用的例子。

//    ARC下 對象的許可權修飾符是__strong

@interface Test : NSObject 
{
     id __strong _obj;
}

- (void)setObject:(id __strong) _obj;
@end

@implementation Test
- (id)init
{
   self = [super init];
   return self;
}

- (void)setObject:(id __strong)obj
{
   _obj = obj;
}

//    以下為迴圈引用。
{
   // test0 持有對象a的強引用
   id test0 = [[Test alloc] init]; // 對象a
   // test1 持有對象b的強引用
   id test1 = [[Test alloc] init]; // 對象b
   // 對象a的obj成員變數持有對象b的強引用
   [test0 setObject:test1];
   //  對象b的obj成員變數持有對象a的強引用
   [test1 setObject:test0];
}

//    因為test0變數超出其作用域,強引用失效,所以釋放對象a
//    因為test1變數超出其作用域,強引用失效,所以釋放對象b
//    此時持有對象a的強引用的變數為對象b的obj
//    此時持有對象b的強引用的變數為對象a的obj
發生記憶體泄露

// 迴圈引用容易發生記憶體泄露,所謂記憶體泄露就是應當廢棄的對象在超出其生存周期後繼續存在。此代碼的本意是賦予變數test0的對象a和對象b在超出其變數作用域時被釋放,即在對象不被任何變數持有的狀態下予以廢棄。但是,迴圈引用使得對象不能被再次廢棄。

像下麵這種情況,雖然只有一個對象,但在該對象持有其自身時,也會發生迴圈引用(自引用)

id test = [[Test alloc] init];

[test setObject:test];

怎麼樣才能避免迴圈引用呢?看到__strong修飾符就會意識到了,既然有strong,就應該有與之對應的weak,也就是說,使用__weak修飾符可以避免迴圈引用。

__weak修飾符與__strong修飾符相反,提供弱引用,弱引用不能持有對象實例。

id __weak obj = [[NSObject alloc] init];
變數附加上了 __weak 修飾符,實際上如果編譯以下代碼,編譯器會發出警告

warning: assigning retained obj to weak variable; obj will be released after assignment [-Warc-unsafe-retained-assign]

 此源代碼將自己生成並持有的對象賦值給附有__weak修飾符的變數obj,即變數obj持有對象持有對象的弱引用。因此,為了不以自己持有的狀態來保存自己生成並持有的對象,生成的對象會立即被釋放。編譯器對此會給出警告。如果像下麵這樣,將對象賦值給附有__strong 修飾符的變數之後再賦值給附有__weak 修飾符的變數,就不會發生警告了。

{
  // 自己生成並持有對象
  id __strong obj0 = [[NSObject alloc] init];
  // 因為obj0變數為強引用,所以自己持有對象,obj1變數持有生成對象的弱引用    
  id __ weak obj1 = obj0;
}
//    因為obj0變數超出其作用域,強引用失效
      所以自動釋放自己持有的對象
      因為對象的所有者不存在,所以廢棄該對象

 因為帶__weak 修飾符的變數(即弱引用)不持有對象,所以在超出其變數作用域時,對象即被釋放如果像下麵這樣將先前可能發生迴圈引用的類成員變數改成附有__weak修飾符的成員變數的話,該現象可以避免。

@interface Test : NSObject
{
   id  __weak obj;
}
- (void)setObject:(id __strong)obj;
@end

//    互相弱引用

對象a             ---->    對象b

id __weak obj    <----    id __weak obj

 __weak修飾符還有另一個優點,在持有某對象的弱引用的時候,若該對象被廢棄,這個弱引用也會被置為nil

id __weak obj0 = nil;
{
   id obj1 = [[NSObject alloc] init]; //    對象a
   obj0 = obj1;
}

//    對象a出了作用域被釋放掉,所以弱引用obj0也會被置為nil

 像這樣,使用__weak可以避免迴圈引用。

 __unsafe_unretained 修飾符

__unsafe_unretained 修飾符正如其名unsafe所示,是不安全的所有權修飾符。儘管ARC式的記憶體管理是編譯器的工作,但附有__unsafe_unretained修飾符的變數不屬於編譯器的記憶體管理的對象,這一點在使用的時候要註意。

id __unsafe_unretained obj = [[NSObject alloc] init];

//    該源代碼將自己生成並持有的對象賦值給附有__unsafe_unretained修飾符的變數中。雖然使用了 unsafe 的變數,但是編譯器不會忽略,而是給出適當的警告。

warning:assignin retained obj to unsafe_unretained variable;obj will be released after assignment

 附有 __unsafe_unretained 修飾符的變數同附有__weak 修飾符的變數一樣,因為自己生成並持有的對象不能繼續為自己所有,所以生成的對象會立即被釋放。到這裡,__unsafe_unretained修飾符和__weak修飾符是一樣的,下麵我們看下源代碼的差異:

id __unsafe_unretained obj1 = nil;
{
// 自己生成並持有對象,因為obj0變數為強引用,所以自己持有對象 id __strong obj0 = [[NSObject alloc] init];
  // 雖然obj0變數賦值給obj1,但是obj1變數既不持有對象的強引用,也不持有弱引用
obj1 = obj0; NSLog(@"A: %@",obj1); }
// 因為obj0變數超出其作用域,強引用失效,所以自動釋放自己持有的對象。因為對象無持有者所以廢棄該對象。
// 輸出obj1 變數表示的對象 obj1變數表示的對象已經被廢棄(懸垂指針) 錯誤訪問 NSLog(@"B: %@",obj1); // 執行的結果為 A : <NSObject :0x753e180> B : <NSObject :0x753e180>

也就是說,最後一行的NSLog只是碰巧正常運行而已。雖然訪問了已經被廢棄的對象,但是應用程式在個別運行狀況下才會崩潰。

再試用__unsafe_unretained修飾符時,賦值給附有__strong修飾符的變數時有必要確保被賦值的對象確實存在。

為什麼需要使用附有__unsafe_unretained修飾符的變數?

比如在iOS4以及 OS X Snow Leopard 的應用程式中,必須使用 __unsafe_unretained修飾符來替代__weak修飾符,賦值給附有 __safe_unretained 修飾符變數的對象在通過該變數使用時,如果沒有確保其確實存在,那麼應用程式就會崩潰。 

__autoreleasing 修飾符

ARC有效時不能使用autorelease方法,另外也不能使用NSAutoreleasePool類。這樣一來,雖然autorelease無法直接使用,但實際上ARC有效時autorelease功能是起作用的。

// ARC無效

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

id obj = [[NSObject alloc] init];

[obj autorelease];

[pool drain];

// ARC有效的時候,該源代碼也能寫成下麵這樣

@autoreleasepool{

    id __autoreleasing obj = [[NSObject alloc] init];

}

 指定“@autoreleasepool塊”來替代“NSAutoreleasePool類對象生成、持有以及廢棄”這一範圍。

另外,ARC有效時,要通過將對象賦值給附加了__autoreleasing 修飾符的變數來替代調用autorelease方法。對象賦值給附有__autoreleasing 修飾符的變數等價於在ARC無效時調用對象的autorelease方法,即對象被註冊到autoreleasepool

可就是說可以理解為,在ARC有效時,用@autoreleasepool塊替代NSAutoreleasePool類,用附有__autoreleasing修飾符的變數替代autorelease方法。

但是,顯示地附加__autoreleasing修飾符同顯式的附加__strong修飾符一樣罕見。

我們通過實例看看為什麼非顯示的使用__autoreleasing修飾符也可以。

取得非自己生成並持有的對象時,如同以下源代碼,雖然可以使用alloc/new/copy/mutableCopy以外的方法來取得對象,但該對象已被註冊到了autoreleasepool。這同在ARC無效時取得調用了autorelease方法的對象是一樣的。這是由於編譯器會檢查方法名是否以alloc/new/copy/mutableCopy開始,如果不是則自動將返回值的對象註冊到autoreleasepool.init方法返回值的對象不會註冊到autoreleasepool。

@autoreleasepool {

//  取得非自己生成並持有的對象

id __strong obj = [NSMutableArray array];

//  因為變數obj為強引用,所以自己持有對象,並且該對象由編譯器判斷其方法名後自動註冊到autoreleasepool       

}

// 因為變數obj超出其作用域,強引用失效,所以自動釋放自己持有的對象。
同時,隨著@autoreleasepool塊的結束,註冊到autoreleasepool中的所有對象被自動釋放,
因為對象的所有者不存在,所以廢棄對象

 像這樣,不適用__autoreleasing修飾符也能使對象註冊到autoreleasepool.以下為取得非自己生成並持有對象時被調用方法的源代碼實例。

+ (id) array{
    return [[NSMutableArray alloc] init];
}

//    該代碼沒有使用__autoreleaseing修飾符,可寫成以下形式

+ (id) array{
    id obj = [[NSMutableArray alloc] init];
    return obj;
}

 因為沒有顯示指定所有權修飾符,所以id obj同附有__strong 修飾符的 id __strong obj是完全一樣的。由於return使得對象變數超出其作用域,所以該強引用對應的自己持有的對象會被自動釋放,但該對象作為函數的返回值,編譯器會自動將其註冊到autoreleasepool。

以下為使用__weak修飾符的例子。雖然__weak修飾符是為了避免迴圈引用而使用的,但在訪問附有__weak修飾符的變數時,實際上必定要訪問註冊到autoreleasepool的對象。

id __weak obj1 = obj0;
NSLog(@"class = %@", [obj1 class]);

// 以下源代碼與此相同

id __weak obj1 = obj0;

id __autoreleasing tmp = obj1;

NSL(@"class = %@", [tmp class]);

 為什麼在訪問附有__weak 修飾符的變數時必須訪問註冊到autoreleasepool的對象呢?這是因為__weak修飾符只持有對象的弱引用,而在訪問引用對象的過程中,該對象有可能被廢棄。如果把要訪問的對象註冊到autoreleasepool中,那麼在@autoreleasepool塊結束之前都能確保該對象存在。因此,在使用附有__weak修飾符的變數時就必定要使用註冊到autoreleasepool中的對象。

最後一個可非顯示的使用 __autoreleasing修飾符的例子,同前面講述的 id obj 和 id __strong obj 完全一樣嗎。那麼id的指針 id * obj 又如何呢?可以由 id __strong obj 的例子類推出 id __strong *obj 嗎? 其實,推出來的是 id __autoreleasing *obj。同樣的,對象的指針 NSObject **obj 便成為了 NSObject *__autoreleasing * obj;

像這樣,id的指針或對象的指針在沒有顯示指定時會被附加上__autoreleasing修飾符。

比如,為了得到詳細的錯誤信息,經常會在方法的參數中傳遞NSError對象的指針,而不是函數返回值。Cocoa框架中,大多數方法也使用這種方式,如NSString 的 stringWithContentsOfFile:encoding:error類方法等。使用該方式的源代碼如下所示:

NSError *error = nil;

BOOL result = [obj performOperationWithError:&error];

該方法的聲明為:

- (BOOL) performOperationWithError:(NSError **)error;

同前面講述的一樣,id的指針或對象的指針會預設附加上__autoreleasing 修飾符,所以等同於以下源代碼

- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error;

 參數中持有NSError對象指針的方法,雖然為響應其執行結果,需要生成NSError類對象,但也必須符合記憶體管理的思考方式。

作為alloc/new/copy/mutableCopy方法返回值取得的對象是自己生成並持有的,其它情況下便是取得非自己生成並持有的對象。因此,使用附有__autoreleasing修飾符的變數作為對象取得參數,與除alloc/new/copy/mutableCopy外其他方法的返回值取得對象完全一樣,都會註冊到autoreleasepool,並取得自己生成並持有的對象。

比如performOperationWithError 方法的源代碼就應該是下麵這樣

- (BOOL) performOperationWithError:(NSError * __autoreleasing *)error{
  
 // 錯誤發生
 *error = [[NSError alloc] initWithDomain:MyAppDomain code:errorCode userInfo:nil];
return NO;

}

// 因為聲明為NSError *__autoreleasing *類型的error作為*error被賦值,所以能夠返回註冊到autoreleasepool中的對象

 然而,下麵的源代碼會產生編譯器錯誤:

NSError *error = nil;

NSError **pError = &error;

賦值給對象指針時,所有權修飾符必須一致。

error:initializing 'NSError *__autoreleasing *' with an expression of type 'NSError *_strong *'changes retain/release properties of pointer 

此時,對象指針必須加__strong 修飾符

NSError *error = nil;
NSError *__strong *pError = &error;
// 編譯正常

當然,對於其他所有權修飾符也是一樣

NSError __weak *error = nil;
NSError * __weak *pError = &error;
// 編譯正常

NSError __unsafe_unretained *unsafeError = nil;
NSError * __unsafe_unretained *pUnsafeError = &unsafeError;
// 編譯正常

 前面的方法參數中使用了附有__autoreleasing 修飾符的對象指針類型。

- (BOOL) performOperationWithError:(NSError * __autoreleasing *)error;

 然而調用方卻使用了附有__strong 修飾符的對象指針類型;

NSError __strong *error = nil;

BOOL result = [obj  performOperationWithError:&error];

 對象指針型賦值時,其所有權修飾符必須一致,但為什麼該源代碼沒有警告就順利通過編譯了呢?實際上,編譯器自動地將該源代碼轉化成了下麵的形式。

NSError __strong *error = nil;

NSError __autoreleasing *tmp = error;

BOOL result = [obj performOperationWithError:&tmp];

error = tmp;

 當然也可以顯示地指定方法參數中對象指針類型的所有權修飾符

- (BOOL) performOperationWithError:(NSError * __strong *)error;

 像該源代碼的聲明一樣,對象不註冊到autoreleasepool也能夠傳遞。但是前面也說過,只有作為alloc/new/copy/mutableCopy 方法的返回值而取得對象時,能夠自己生成並持有對象。其他情況即為“取得非自己生成並持有的對象”,這些務必牢記。為了在使用參數取得對象時,貫徹記憶體管理的思考方式,我們要將參數聲明為附有__autoreleasing修飾符,但在顯示的指定 __autoreleasing 修飾符時,必須註意對象變數要為自動變數(包括局部變數、函數以及方法參數)。

 

下麵我們換個話題,詳細瞭解一下@autoreleasepool.如以下源代碼所示,ARC無效時,可以將NSAutoreleasePool對象嵌套使用。

// ARC無效
NSAutoreleasePool *pool0 = [[NSAutoreleasePool alloc] init];

NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc] init];

NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];

id obj = [[NSObject alloc] init];

[obj autorelease];

[pool2 drain];

[pool1 drain];

[pool0 drain];

同樣的,@autoreleasepool 也能夠嵌套使用

@autoreleasepool{
    @autoreleasepool{
         @autoreleasepool{
          id __autoreleasing obj = [[NSObject alloc] init];
    }
  }
}

 比如,在iOS應用程式模板中,像下麵的main函數一樣,@autoreleasepool塊包含了全部程式。

int main(int argc, char *argv[]){
  @autoreleasepool{
   return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

 NSRunLoop等實現不論ARC有效還是無效,均能夠隨時釋放註冊到autoreleasepool中的對象。

另外,如果編譯器版本為LLVM3.0以上,即使ARC無效@autoreleasepool塊也能夠使用,如下:

// ARC無效
@autoreleasepool{
    id obj = [[NSObject alloc] init];
    [obj autorelease];
}

 因為autoreleasepool 範圍以塊級源代碼表示,提高了程式的可讀性,所以今後在ARC無效時也推薦使用 @autoreleasepool塊。

另外,無論ARC是否有效,調試用的非公開函數_objc_autoreleasePoolPrint()都可以使用

_objc_autoreleasePoolPrint()

利用這一函數可有效的幫助我們調試註冊到autoreleasepool 上的對象。

1.3.4 規則

- 不能使用 retain/realese/retainCOunt/autorelease

- 不能使用 NSAllocateObject/NSDeallcateObject

- 必須遵守記憶體管理的方法命名規則

- 不要顯示調用dealloc

- 使用@autoreleasepool代替 NSAutoreleasePool

- 不能使用區域 (NSZone)

- 對象型變數不能作為C語言結構體的成員

- 顯示轉化 id  和 void *

在ARC無效時,像一下代碼這樣將id變數強制轉換成 void * 變數並不會出問題。

// ARC無效
id obj = [[NSObject alloc] init];

void *p = obj;

更近非同步,將該void * 變數賦值給 id 變數中,調用其實例方法,運行時也不會有問題。

// ARC無效

id o = p;

[o release];

// 但是在ARC有效時這便會引起編譯錯誤

error : implicit coversion of an OC pointer to 'void *' is disallowed with ARC

 id 型或對象型變數賦值給 void * 或者逆向賦值時都需要進行特定的轉換。如果只想單純的賦值,則可以使用 "__bridge 轉換"

id obj = [[NSObject alloc] init];

void *p = (__bridge void *)obj;

id o = (__bridge id)p

 像這樣,通過 “__bridge轉換”, id 和 void * 就能夠相互轉換。

但是轉換為 void 的 __bridge 轉換,其安全性與賦值給 __unsafe_unretained 修飾符相近, 甚至會更低。如果管理時不註意賦值對象的所有者,就會因懸垂指針導致程式崩潰。

__bridge 轉換中還有另外兩種轉換,分別是 “__bridge_retained 轉換” 和 “__bridge_transfer” 轉換。

id obj = [[NSObject alloc] init];

void *p = (__bridge_retained void *)obj

 __bridge_retained 轉換可使要轉換賦值的變數也持有所賦值的對象。 。。。。。

1.3.5 屬性

當ARC有效時,oc類的屬性也會發生變化

@property (strong ,nonatomic) NSString *name;

 當ARC有效時,以下可作為這種屬性聲明中使用的屬性來用。

屬性聲明和屬性和所有權修飾符的對應關係
屬性聲明的屬性 所有權修飾符
assign __unsafe_unretained修飾符
copy   __strong 修飾符(但是賦值的是被賦值的對象)
retain __strong 修飾符
strong __strong 修飾符
unsafe_unretained __unsafe_unretained 修飾符
week __weak 修飾符

以上各種屬性賦值給指定的屬性中就相當於賦值給附加各屬性對應的所有權修飾符的變數中,只有copy屬性不是簡單的賦值,它賦值的是通過NSCopying 結構的 copyWithZone 方法複製賦值源所生成的對象。

另外,在聲明類成員變數時,如果同屬性聲明中的屬性不一致則會引起編譯錯誤。比如下麵的這種情況,

id obj

在聲明 id obj 成員變數時,像下麵這樣,定義其屬性為weak

編譯器會報錯。

此時,成員變數的聲明中需要附加 __weak 修飾符。

id __weak obj;

或者使用 strong 屬性替代 weak 屬性

@property (nonatomic, strong ) id obj

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 vue這個框架現在挺流行的,作為一個專註前端100年的代碼愛好者,學習下目前流行的框架是必須的!在網上搜索vue的項目是比較少的,在官網進行了入門學習後,沒有一個項目練習鞏固下,學了就等於沒學,所以我就決定自己寫一個項目咯。在這裡我也順便分享下我學習vue的資源。我在GitHub上發現了一個v ...
  • 一、簡述 最近跟小伙伴一起討論了一下,決定一起仿一個BiliBili的app(包括android端和iOS端),我們並沒有打算把這個項目完全做完,畢竟我們的重點是掌握一些新框架的使用,併在實戰過程中發現並彌補自身的不足。 本系列將記錄我(android端)在開發過程中的一些我覺得有必要記錄的功能實現 ...
  • 前言 在面試過程中你也許會被問到消息轉發機制。這篇文章就是對消息的轉發機制進行一個梳理。主要包括什麼是消息、靜態綁定/動態綁定、消息的傳遞和消息的轉發。接下來開發進入正題。 消息的解釋 在其他語言裡面,我們可以用一個類去調用某個方法,在OC裡面,這個方法就是消息。某個類調用一個方法就是向這個類發送一 ...
  • Android O新增的一個特性,系統會在通知欄顯示當前在後臺運行的應用,其實際是顯示啟動了前臺服務的應用,並且當前應用的Activity不在前臺。具體我們看下源碼是怎麼實現的。 1 APP調用 或`startForegroundService`啟動一個service. 和`startForegro ...
  • 通過友盟sdk集成微博、微信、qq等分享功能時,微博和qq很順利,但在做微信集成時一直不成功。主要問題還是之前在微信開放平臺申請創建移動應用時,對應用簽名沒有填寫對,走了很多彎路現總結出來,加深記憶避免後繼彎路。在這裡微信開放平臺的註冊、移動應用的創建就不做說明瞭,需要註意的是提交申請後騰訊需要一周 ...
  • 每次切換線程的操作 變換封裝操作 使用的時候 水一波 ...
  • App啟動卡慢會影響一個App的卸載率和使用率。啟動速度快會給人一種輕快的感覺,減少用戶等待時間。如果一個App從點擊桌面圖標到看到主界面花了10秒,請問你能接受麽?忍耐不好的估計直接就卸載了,或者沒等打開就直接Home鍵按出去,然後殺進程了。這樣一來App卸載率提升了,使用率下降了。所以對於有大量... ...
  • 1.KVO簡介 KVO是Objective-C對觀察者設計模式的一種實現,它提供一種機制,指定一個被觀察對象(如A類),當對象中的某個屬性發生變化的時候,對象就會接收到通知,並作出相應的處理。在MVC設計架構下的項目,KVO機制很適合實現mode模型和view視圖之間的通訊。例如:代碼中,在模型類A ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...