iOS 讀書筆記

Pro Multithreading And Memory Management for iOS and OS X

這本書是看網路Blog時有人介紹的,主要內容就三章ARC BLOCK GCD

作者是兩個日本人寫的,衝著自己對Block需要再加強,所以興衝衝去買了簡體中文版,我是覺得需要看熟的東西還是不要考驗自己英文比較好 w 而且外國人也說翻的很爛ww。

這部很好看,超展開 OP2 也很熱血好聽~打翻你對英雄的印象。

一開始先來介紹一下

  • #ARC

一開始是講引用計數和內存管理,因為自己這方面也算熟,所以跳過不看了。
直接跳到重點 “ARC”

ARC下有四種所有權修飾符

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

__string

所有的id類型和對象類型默認都是__strong 的
也就是說
id obj = [[NSobject alloc]init]
等價於

id __strong obj = [[NSobject alloc]init]

即使有__strong 修飾符下,超出了變數的作用域(scope),引用還是會消失的
也就是

1
2
3
4
5
6
7
8
9
10
{
id __strong obj = [[NSObject alloc]init];
/* 因為變數obj 為__strong
* 所以自己持有NSObject 對象 也就是retainCount + 1
*/
}
/* 變數obj 超出作用域,強引用失效
* 所以自動釋放自己持有的對象
*/ 對象的持有者不存在了,因此此對象被廢棄

同理 適用在 [NSMutableArray array] 這種帶有autorelease的返回值。

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

1
2
3
4
5
6
7
8
9
id __strong obj1 = [[NSObject alloc]init];
id __strong obj2 = [[NSObject alloc]init];
id __strong obj3 = nil;
obj1 = obj2;
obj3 = obj1;
obj2 = nil;
obj1 = nil;
obj3 = nil;

讓我們看一下內存分析

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
id __strong obj1 = [[NSObject alloc]init]; //對象A
/*
* obj1 持有對象A的強引用
*/
id __strong obj2 = [[NSObject alloc]init]; //對象B
/*
* obj2 持有對象B的強引用
*/
id __strong obj3 = nil;
/*
* obj3 不持有任何對象
*/
obj1 = obj2;
/* obj1 持有由obj2 賦值的對象B強引用
* 因為obj1 被賦值,原先持有對象A的引永失效,所以對象A被廢棄
* 此時,對象B為obj1 和 obj2 的持有對象。
*/
obj3 = obj1;
/* 同理,因為 obj3本身就沒持有對象,所以此時持有對象B的引用為
* obj 1,2,3
*
*/
obj2 = nil;
/* nil被賦給了obj2所以obj2對對象B的引用失效
* 此時持有對象B的引用為 obj1 obj3
*/
obj1 = nil;
/*
* 同理 對象B剩obj3 持有引用
*/
obj3 = nil;
/*
* 一樣狀況nil賦予obj3,obj3對對象B的引用失效
*/ 因此對象B被廢棄
安全~完全沒有Memory leak問題

需特別說明一下__strong__weak, __autoreleasing 都保証有這些修飾符時,(自動變數)區域變數的初始化為nil。
也就是

1
2
3
id __strong obj; == id __strong obj = nil;
id __weak obj1; == id __weak obj1 - nil;
id __autoreleasing obj2; == id __autoreleasing obj2 = nil;

__weak

為什麼要有weak出現呢,因為用了strong後,會產生循環引用的問題。也就是兩個物件互相有了對方的強引用。
下面情況也會產生循環引用

1
2
id test = [[test alloc]init]; //自己引用自己
[test setObject:test];

__weak__strong不同,提供了弱引用,也就是不持有對象實例,需注意的是__weak不能顯示的修飾一個Object 如:

1
2
3
4
5
6
7
8
id __weak obj = [[NSObject alloc]init];
此處會產生警告,因為為了不讓自己生成並持有的對象立即被釋放。
必須做以下處理
{
id __strong obj = [[NSObject alloc]init];
id __weak obj1 = obj;
}

同樣的當它引用的對象被廢棄時,持有對象的弱引用也失效,並賦值nil給 obj1
也就是__weak的特色,當它引用的對象不存在時,ARC會自動幫你賦值nil,以避免野指標。

__unsafe_unretained

用法其實跟assign 差不多,不過當它引用的對象不存在時,ARC並不會幫你自動賦值nil,所以必須手動設置,跟assign一樣用法即可。

__autoreleasing

在ARC中,通過將對象附加了__autoreleasing修飾符的變數等價於在ARC無效時,調用對象的autorelease方法,即被註冊到autoreleasePool中。

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

不過直接使用__autoreleasing 的情況跟__strong一樣很少,為什麼呢?

因為我們都知道調用class method的對象返回值都是帶有autorelease的,同理,在一個Method中做為返回值時,也是會幫你註冊到autoreleasepool中的。如:

1
2
3
4
5
+ (id)array
{
id obj = [[NSMutableArray alloc]init];
return obj; //返回值自動註冊到autoreleasepool
}

另一種情況是使用__weak也會自動幫你註冊到自動釋放池,為什麼呢?
因為__weak 只持有對象的弱引用,而在訪問對象的過程中,該對象有可能被廢棄,如果把訪問對象加到autoreleasepool中,䢷麼在@autorelease 結束前都能確表該對象存在。

所以使用附有__weak修飾符的變數就必定要註冊到aotoreleasepool中的對象。

最後一個非顯示使用__autoreleasing的情況就是,我們都是id 預設是__strong,但是如果是id類型的指標呢 (id *obj)

會變成這樣 id __autoreleasing *obj 同樣的,對象指標 NSObject ** obj 則變成了
NSObject *__autoreleasing *obj

看到了兩個符號有想到Objc中那邊用的最多呢?沒錯~就是`NSError *error`

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

但下面會產生編譯錯誤

1
2
3
4
5
6
NSError *error = nil;
NSError **error1 = &error;
賦值給對象指標時,修飾符必須前後一致,所以需改成
NSError *error = nil;
NSError *__strong *error1 = &error;

此道理同__weak__unsafe_unretained

如果當返回時是一個__strong指標類型的話呢?

1
2
3
4
5
6
7
8
NSError __strong *error = nil;
BOOL result = [obj performSelectWithError:&error]; -->???
編譯器會轉化成下面形式
NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
BOOL result = [obj performSelectWithError:&tmp];
error = tmp;

當然也可以顯示的指定指標類型的所有權修飾符(NSError *__strong *error)

###ARC 有幾個規則,讓我們一一對照一下

  • 不能使用retain/release/retainCount/autorelease
  • 不能使用 NSAllocateObject/NSDeallocateObject
  • 須尊守記憶體管理的方法命名規則
  • 不要顯示的調用dealloc
  • 使用@autoreleasepool塊替代NSAutoreleasePool
  • 不能使用NSZone
  • 對象型變數不能做為C語言struct中的成員
  • 顯示的轉換”id” 和 “void *”

前六項其實都沒什麼好講,有一點概念的應該都知道這些,第七項struct 問題
當你在struch中寫入objc的對象時,會出現錯誤

可以加__unsafe_unretained修飾符或強制轉換void *(下面講)

1
2
3
4
5
6
7
struct Date {
NSArray *array
改成
NSArray __unsafe_unretained *array
但需注意__unsafe_unretained 不屬於編譯器的記憶體管理對象,需自己手動管理,如果稍不注意有可能會引發程式崩潰,需注意此點。
};

第八點轉換id 和void* 其實就是__bridge __bridge_retained __bridge_transfer
不過這些轉換通常都用於Foundation和Core Foundation中的對象,所以不是今天想講的內容。有興趣可以自行查詢。

在ARC下,類型的屬性也會發生變化。(Property)

屬性的聲明 所有權修飾符
assign __unsafe_unretained 修飾符
copy __strong 修飾符(但是是賦值到被複製的對象)
retain __strong 修飾符
strong __strong 修飾符
unsafe_unretained unsafe_unretained 修飾符
weak __weak 修飾符

所以我朋友說他使用block用strong也可以,因為我記得block是要用copy的,在ARC下會自動幫你管理的。


那我們再來討論一下賦值給__strong __weak __autoreleasing的情況下,程式到底是怎麼運行的呢?

__strong

1
2
3
4
5
6
7
8
{
id __strong obj = [[NSObject alloc]init];
在編譯器選項"_S"的同時運行clang 可取得程式組合輸出,會轉變為
/* 編譯器的組合代碼*/
id obj = objc_msgSend(NSObject,@selector(alloc));
obj_msgSend(obj,@selector(init));
objc_release(obj);
}

雖然調用了兩msgSend()方法,變數作用域結束時,通過objc_release釋䢍為象。雖然ARC下不能使用release,但顯然的編譯器自動幫我們插入了release。

那看看由class method時會是什麼情況

1
2
3
4
5
6
7
8
{
id __strong obj = [NSMutableArray array];
/* 編譯器的組合代碼*/
id obj = objc_msgSend(NSMutableArray,@selector(array));
obj_retainAutoreleasedReturnValue(obj);
objc_release(obj);
}

中間程式碼中多了一行obj_retainAutoreleasedReturnValue,那麼什麼是obj_retainAutoreleasedReturnValue呢,它主要用于最優化程式運行。

顧名思義,它用于自己持有(retain)對象,但它持有的對象應為返回註冊到autoreleasepool中的對象,也等同於函數的返回值。

obj_retainAutoreleasedReturnValue是成對的,相對的是obj_autoreleasedReturnValue,它用於當alloc這些以外的class方法返回對象的時候。

1
2
3
4
5
6
7
8
+ (id)array
{
return [[NSmutableArray alloc]init];
/* 編譯器的組合代碼*/
id obj = objc_msgSend(NSMutableArray,@selector(alloc));
obj_msgSend(obj,@selector(init));
return obj_autoreleasedReturnValue(obj);
}

那麼obj_autoreleasedReturnValue有什麼作用呢?

obj_autoreleasedReturnValue會檢查使用該函數的方法或調用方的執行命令表,如果方法或函數的調用方裡有obj_retainAutoreleasedReturnValue時,他就不會再次註冊到autoreleasepool中而是直接返回方法或函數。

<達到了最佳化>

__weak

  • 若附有__weak修飾符的變數所引用的對象不存在時,則nil賦值給該變數。
  • 使用附有__weak修飾符的變數,即是使用註冊到autoreleasepool中的對象。

唯一需要注意的是,如果大量使用附有__weak修飾符的變數時,則會消耗相應的CPU資,比較好的方法是只在需要避免循環引用時使用__weak修飾符。

因為使用__weak會被自動註冊到autoreleasepool中,所以重複的使用同一個變數時,也會重複的註冊到autoreleasepool中

1
2
3
4
5
6
7
8
9
10
11
id __weak tmp = obj;
NSlog("%@",tmp);
NSlog("%@",tmp);
NSlog("%@",tmp);
/* 這邊會產生三個autoreleasepool 相當的耗CPU*/
//解決方法如下
id __weak tmp = obj;
id tmpObj = tmp;
NSlog("%@",tmpObj);
NSlog("%@",tmpObj);
NSlog("%@",tmpObj);

在 “tmp = obj” 時,對象註冊到autoreleasepool中只有1次。

__autoreleasing

其實內部實現跟weak不太一樣,不過大致上跟__weak 差不多,現在iOS開發上也不需要支持iOS 4的機器,所以幾乎可以不用太管這個修飾符。