iOS的記憶體管理和引用計數規則、Block的用法以及三種形式(stack、malloc、global)
阿新 • • 發佈:2020-05-10
### 學習內容
1. iOS的記憶體管理和引用計數規則
- 記憶體管理的思考方式
- 自己生成的物件自己持有
- 非自己生成的物件自己也能持有
- 自己持有的物件不需要時釋放
- 非自己持有的物件不能釋放
- ARC有效時,id型別和物件型別必須加上所有權修飾符,一共有四種
- __strong
- id和物件型別如果不加所有權修飾符那麼預設為__strong型別
- ```objective-c
id obj = [[NSObject alloc]init]
id __strong obj = [[NSObject alloc]init]
//以上兩種在ARC有效情況下是相同的
```
- ```objective-c
//ARARC
{
id __strong obj = [[NSObject alloc]init]
}
//ARC無效時
{
id obj = [[NSObject alloc]init]
[obj release]
}
//ARC無效時執行release操作
```
- __strong修飾符表示對物件的強引用,持有強引用的變數在超出其作用域時被廢棄,它強引用的物件也會被釋放
- 含有__strong修飾的變數,不僅僅在作用域上,在賦值上也能正確管理物件的生命週期
- __weak
- __strong所有權修飾符大部分情況下可以完美的進行內潤管理,但是引用計數式記憶體管理方法必然會遇到迴圈引用,這時候就需要使用\__weak來解決
- 迴圈引用會造成記憶體洩漏,所謂記憶體洩漏就是應當被廢棄的物件在超出其生存週期(作用域)後繼續存在
- __weak修飾的變數不持有物件,原物件在超出其作用域時會被立即釋放
- 通過檢查__weak修飾符的變數是否為nil,可以判斷被賦值的物件是否已經被釋放
- __unsafe_unretained
- 這是不安全的所有權修飾符
- 附有__unsafe_unretained的變數不屬於編譯器的記憶體管理物件
- 如果物件已經被釋放,但是__unsafe_unretained修飾的變數仍然訪問了原物件的記憶體,那麼這個變數就是個懸垂指標,錯誤訪問,大概率會引起程式的崩潰
- __autoreleasing
- 在ARC有效時,用@autoreleasepool塊代替NSAutoreleasePool類,用附有__autoreleasing修飾符的變數來代替autorelease方法
- 編譯器會自動檢查方法名是否為alloc/new/copy/mutablecopy開頭,如果不是的話則自動將返回值的物件註冊到autoreleasepool中(init方法返回值的物件也不註冊到autoreleasepool中)
- 屬性宣告的屬性修飾符與所有權修飾符之間的關係
- | 屬性修飾符 | _unsafe_unretained |
| ----------------- | ------------------------------------------- |
| assign | _unsafe_unretained |
| copy | _strong(指標變數指向的是新的被複制的物件) |
| retain | _strong |
| strong | _strong |
| unsafe_unretained | _unsafe_unretained |
| weak | _weak |
- 為屬性新增各種修飾符就相當於給變數新增各種對應的所有權修飾符
- ```objective-c
@property (strong) id obj;
--------------------------
id __strong obj = [[NSObject alloc]init]
```
-
- ARC的規則
- 不能使用retain/retainCount/release/autoRelease
- 不能使用NSAllocateObject和NSDeallocateObject
- 遵守記憶體管理的方法命名規則
- 不要顯式的呼叫dealloc
- 使用@autoreleasepool代替NSAutoreleasePool
- 不能使用NSZone
- 物件型變數不能作為c語言結構體的成員
- 顯式轉換"id"和"void *"
2. Block
- block是什麼
- ```objective-c
//一句話概括,block是帶有自動變數的匿名函式
^(int param){
NSLog(@"%d",param);
}
-------------------------------------
//上面的為簡寫的,完整的block形式為
//^返回值型別 (引數列表){表示式}
^void (int param){
NSLog(@"%d",param);
}
//完整形式的block語法與C語言函式定義相比,僅有兩點不同
//1.沒有函式名---沒有函式名因為它是匿名函式
//2.帶有"^"符號---^是插入記號,方便查詢
```
- block的返回值型別可以省略,省略返回值型別時
- 如果表示式中有return語句,那麼返回值型別就和return的相同
- 沒有return,返回值型別就是void
- 有多個return語句時,所有return的型別必須相同
- block的引數型別也可以省略
- ```objective-c
^(void)(void){expression}
-------------------------
^{expression}
```
- ```objective-c
//C語言的函式指標的寫法
int func(int a){
printf("%d",a);
}
int (*funcptr)(int) = &func //將func函式的地址賦值給函式指標funcptr
//block的寫法
int (^blk)(int a); //僅僅是將c語言函式指標的"*"更換成了"^"
```
- block型別變數與c語言變數的用法完全相同,可以作為以下用途使用
- 自動變數(區域性變數)
- 函式引數
- 靜態變數
- 靜態全域性變數
- 全域性變數
- block變數的使用
- ```objective-c
//將block賦值給block變數
int (^blk)(int) = ^(int){};
//將block變數賦值給block變數
int (^blk1)(int) = blk;
//兩個block變數互相賦值
int (^blk2)(int);
blk2 = blk1;
```
- ```objective-c
//在函式引數中使用block型別變數可以向函式傳遞block
void func((int)(^blk)(int));
//在函式返回值中使用block可以將返回值指定為block
void func(){
return ^{return;};
}
```
- ```objective-c
//使用typedef定義block
typedef (int)(^blk)(int); //定義一個block,後面該block的型別就為blk
//通過block型別變數呼叫block與在c語言中通常的函式執行沒什麼區別
blk func(blk block,int rate){
return blk(rate);
}
```
- ```objective-c
//這裡blk截獲的是Array物件的例項指標,通過這個例項指標呼叫該物件的方法是完全沒問題的,但是如果向Array指標賦值的話就會編譯錯誤(可以用__block解決)
id Array = [NSMutableArray new];
void (^blk)(void) = ^{
id obj = [NSObject new];
[Array addObject:obj];
};
```
- 在block中如果需要改變被截獲的外部變數的值,可以使用__block說明符(__block儲存域類說明符)來解決
- block程式碼轉換為cpp程式碼分析(待完善)
```objective-c
//block對外部變數捕獲的原理,使用cpp程式碼檢視
//這裡寫一個block捕獲外部變數val的值
int val = 10;
void (^blk)(void) = ^{
printf("%d",val);
};
//block本質也是一個OC的物件,oc物件都是結構體,其中含有指向父類的isa指標
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//這裡使用clang -rewrite-objc將.m程式碼轉換成.cpp程式碼,這裡的_cself是block的自身的結構體指標,可以看到在block的函式中是將val重新建立了一個變數進行輸出
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
printf("%d",val);
}
```
- 三種block的形式
1. NSConcreteGlobalBlock(全域性Block,存放在全域性區[資料區])
- 記錄全域性變數的地方有Block語法時
- Block語法的表示式中不使用應截獲的自動變數時
2. NSConcreteStackBlock(棧Block)
- 除了1.中的兩種情況生成的Block,其他使用Block語法產生的Block都是棧Block
3. NSConcreteMallocBlock(堆Block)
- 配置在全域性區的block在變數作用域外也可以通過指標安全的訪問,但是配置在棧上的block一旦其作用域結束就會被系統回收
- Block提供了將Block和__Block變數複製到堆上的方法來解決這個問題
- 複製到堆上的block將類物件NSMallocBlock賦值給isa指標
```objective-c
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
//注意這裡就是講isa指標指向堆Block
impl.isa = &_NSConcreteStackBlock;
}
};
```