1. block的本質
- block本質上也是一個OC對象,它內部也有個isa指針。
- block是封裝了函數調用以及函數調用環(huán)境(block函數的調用地址杠人、參數、變量等信息)的OC對象宋下。
- block的底層結構代碼如下:
- 首先在main函數中申明一個block
// 首先在main函數中申明一個block
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 20;
// 申明一個block
void (^block)(int, int) = ^(int a , int b){
NSLog(@"this is a block! -- %d", age);
NSLog(@"this is a block!");
NSLog(@"this is a block!");
NSLog(@"this is a block!");
};
}
return 0;
}
2.將main函數的oc代碼轉成C++代碼嗡善,具體看下block的底層實現結構如下:
// 將main函數的oc代碼轉成C++代碼,具體看下block的底層實現結構如下:
// oc中申明的block代碼底層實現是一個__main_block_impl_0的結構體
struct __main_block_impl_0 {
// impl:是__block_impl類型的結構體学歧,其內部有個isa指針罩引,所以block的本質是一個oc對象。
struct __block_impl impl;
// Desc:是__main_block_desc_0類型的結構體枝笨。
struct __main_block_desc_0* Desc;
// age:是main函數中申明的局部變量age
int age;
// c++的構造方法蜒程,類似于oc的init構造方法
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// impl的結構體內部實現:
struct __block_impl {
void *isa;
// 默認為0
int Flags;
int Reserved;
// FuncPtr:block內部函數執(zhí)行地址
void *FuncPtr;
};
// Desc的結構體內部實現:
static struct __main_block_desc_0 {
size_t reserved;
// Block_size:block的內存空間大小
size_t Block_size;
}
-
block的底層結構如右圖所示:底層結構.png
2. block的變量捕獲(capture)
為了保證block內部能夠正常訪問外部的變量,block有個變量捕獲機制伺帘。判斷會不會被捕獲的標準是:如果是全局變量不會捕獲昭躺,如果是局部變量則會捕獲。捕獲機制.png
代碼演示如下:
// auto:自動變量伪嫁,離開作用域就銷毀(平時申明的變量前面默認auto類型领炫,auto是省略了)
auto int age = 10;
static int height = 10;
void (^block)(void) = ^{
// age的值捕獲進來(capture)height的地址捕獲進來
NSLog(@"age is %d, height is %d", age, height);
};
// 值傳遞
age = 20;
// 指針傳遞
height = 20;
// 打印結果:age is 10, height is 20
block();
注意:self也是一個局部變量,所以也會被捕獲张咳。所以通過self訪問的變量也都會被捕獲帝洪。方法調用中,c++底部所有的方法調用都會默認傳遞self和_cmd(方法名)兩個參數脚猾,傳遞的參數就是局部變量葱峡。默認方法入參.png
3. block的類型
(1) block有3種類型,可以通過調用class方法或者isa指針查看具體類型龙助,最終都是繼承自NSBlock類型砰奕。
-
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
存放在數據區(qū)域。沒有訪問auto變量時提鸟,就是該類型block军援。 -
__NSStackBlock__ ( _NSConcreteStackBlock )
存放在棧段。訪問了auto變量時称勋,就是該類型block胸哥。 -
__NSMallocBlock__ ( _NSConcreteMallocBlock )
存放在堆段。NSStackBlock類型block調用了copy函數時就是該類型赡鲜。
存儲位置示意圖:存儲位置.png - 程序區(qū)域:程序編譯時空厌,將代碼相關數據存儲在此區(qū)域庐船。無需開發(fā)者管理。
- 數據區(qū)域:程序編譯時嘲更,全局變量數據存儲在此區(qū)域醉鳖。無需開發(fā)者管理。
- 堆:程序運行時哮内,動態(tài)分配內存,需要開發(fā)者申請內存盗棵,也需要開發(fā)者自己管理內存。
- 棧:系統(tǒng)自動分配內存北发,自己銷毀纹因。存放局部變量數據,離開作用域時內存銷毀琳拨。
調用copy結果.png
(3) block的copy操作:
- 在ARC環(huán)境下瞭恰,編譯器會根據情況自動將棧上的block復制到堆上(block會變成NSMallocBlock類型),比如以下情況:
1. block作為函數返回值時狱庇;
2. 將block賦值給__strong指針時惊畏;
3. block作為Cocoa API中方法名含有usingBlock的方法參數時;
4. block作為GCD API的方法參數時密任;
- ARC下block屬性的建議寫法:
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
- MRC下block屬性的建議寫法:
@property (copy, nonatomic) void (^block)(void);
4. block內部訪問對象類型的auto變量
當block內部訪問了對象類型的auto變量時:
- 如果block是在棧上颜启,將不會對auto變量產生強引用。
- 如果block被拷貝到堆上:1. 會調用block內部的copy函數浪讳;2. copy函數內部會調用
_Block_object_assign
函數缰盏;3._Block_object_assign
函數會根據auto變量的修飾符(__strong、__weak淹遵、__unsafe_unretained)
做出相應的操作口猜,形成強引用(做一次retain操作)或者弱引用; - 如果block從堆上移除透揣。1. 會調用block內部的
dispose
函數济炎;2. dispose函數內部會調用_Block_object_dispose
函數;3._Block_object_dispose
函數會斷開對auto變量的引用(做一次release操作)辐真;函數調用時機.png
5. block關于__block修飾變量
- __block可以用于解決block內部無法修改auto變量值的問題须尚。
- __block不能修飾全局變量、靜態(tài)變量(static)拆祈。
- 編譯器會將__block修飾符的變量包裝成一個對象恨闪。底層掩飾如下
// 申明一個__block修飾符變量
typedef void (^MJBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
MJBlock block1 = ^{
age = 20;
NSLog(@"age is %d", age);
};
block1();
}
return 0;
}
// 上述oc代碼轉成c++底層代碼倘感,__block int age的結構
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
/* __block int age變量被包裝成__Block_byref_age_0類型的結構體放坏,結構體里
有isa指針,實質是oc對象老玛。
block修改age的值是通過*age ->forwarding->age來修改的
*/
__Block_byref_age_0 *age;
};
// __Block_byref_age_0結構體:
struct __Block_byref_age_0 {
void *__isa;
// 存放指向自己的內存地址
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
// __block int age變量 age的值
int age;
};
-
__block修飾變量的內存管理
當block在棧上時淤年,并不會對__block變量產生強引用
-
當block被copy到堆時:1. 會調用block內部的copy函數钧敞;2. copy函數內部會調用
_Block_object_assign
函數;3._Block_object_assign
函數會對__block變量形成強引用(做一次retain操作);引用流程圖.png -
當block從堆中移除時: 1. 會調用block內部的
dispose
函數麸粮;2. dispose函數內部會調用_Block_object_dispose
函數溉苛;3._Block_object_dispose
函數會斷開對__block變量的引用(做一次release操作);移除流程圖.png
-
__block修飾的對象變量內存管理
- 當block在棧上時弄诲,并不會對__block變量產生強引用愚战;
- 當block被copy到堆時:1. 會調用block內部的copy函數;2.
_Block_object_assign
函數會根據所指向對象的修飾符(__strong齐遵、__weak寂玲、__unsafe_unretained)
做出相應的操作,形成強引用(retain)或者弱引用(注意:這里僅限于ARC時會retain梗摇,MRC時不會retain)拓哟;3._Block_object_assign
函數會對__block變量形成強引用(做一次retain操作); - 當block從堆中移除時: 1. 會調用block內部的
dispose
函數伶授;2. dispose函數內部會調用_Block_object_dispose
函數断序;3._Block_object_dispose
函數會斷開對__block對象變量的引用(做一次release操作);
-
__block修飾變量的__forwarding指針糜烹。
這樣做的原因是:如果block在棧上時, __forwarding指針指向是棧上的block挚币, 如果block copy到堆上時亮蒋, __forwarding指針指向的是堆上的block, 通過__forwarding指針來訪問變量妆毕,就可以保證訪問的變量是堆上的變量慎玖。流程圖如下:
上面提到违诗,__block修飾符變量的底層是包裝成一個oc對象,其內部有一個指向自己的__forwarding指針疮蹦,訪問__block變量是通過__forwarding訪問自己內部的__block變量较雕。
訪問流程.png
6. block循環(huán)引用問題
- 什么是block循環(huán)引
循環(huán)引用是指對象之間的強引用鏈形成了環(huán)就創(chuàng)造了一個循環(huán)引用。最簡單的情況笛粘,兩個對象之間強引用趁怔,你引用我,我引用你薪前,導致內存無法釋放润努,就形成了循環(huán)引用。
block 的循環(huán)引用情況是示括,block 會捕獲內部使用的對象铺浇,形成隱式的強引用,一般有以下兩種常見的情況:- 引用 self:直接寫 self:
2.成員變量:不寫 self垛膝,但實際上還是對 self 的強引用:self.callback = ^{ NSLog(@"callback: %@", self);}
self.callback = ^{ NSLog(@"callback: %@", _name); // 等價于 NSLog(@"callback: %@", self->_name); }
- ARC-解決循環(huán)引用問題
- 用__weak解決,不會產生強引用鳍侣,指向的對象銷毀時丁稀,會自動讓指針置為nil。
MJPerson *person = [[MJPerson alloc] init]; __weak typeof(person) weakSelf = person; person.block = ^{ NSLog(@"age is %d", weakSelf.age); }
- 用__unsafe_unretained解決,不會產生強引用倚聚,不安全线衫,指向的對象銷毀時,指針存儲的地址值不變惑折,所以一般不常用授账。
MJPerson *person = [[MJPerson alloc] init]; __unsafe_unretained typeof(person) weakPerson = person; person.block = ^{ NSLog(@"age is %d", weakPerson.age); };
-
用__block解決(必須要調用block),缺點:1. 必須要將block強引用的對象置空,且block一定要調用惨驶;2. 一定要等到block執(zhí)行完矗积,對象才能被釋放。如果這個block一直沒有被調用敞咧,對象就一直不會被釋放棘捣,就會存在內存泄露。block解決循環(huán)引用示意圖.png
代碼演示:
__block MJPerson *person = [[MJPerson alloc] init]; person.block = ^{ person.age = 20; NSLog(@"age is %d", person.age); person = nil }; person.block();
- MRC-解決循環(huán)引用問題(不支持__weak)
-
用__unsafe_unretained解決image.png
-
用__block解決image.png
-
用__unsafe_unretained解決