1. block的本質(zhì)
- block本質(zhì)上也是一個OC對象,它內(nèi)部也有個isa指針。
- block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境(block函數(shù)的調(diào)用地址、參數(shù)、變量等信息)的OC對象。
- block的底層結(jié)構(gòu)代碼如下:
- 首先在main函數(shù)中申明一個block
// 首先在main函數(shù)中申明一個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函數(shù)的oc代碼轉(zhuǎn)成C++代碼,具體看下block的底層實現(xiàn)結(jié)構(gòu)如下:
// 將main函數(shù)的oc代碼轉(zhuǎn)成C++代碼,具體看下block的底層實現(xiàn)結(jié)構(gòu)如下:
// oc中申明的block代碼底層實現(xiàn)是一個__main_block_impl_0的結(jié)構(gòu)體
struct __main_block_impl_0 {
// impl:是__block_impl類型的結(jié)構(gòu)體,其內(nèi)部有個isa指針,所以block的本質(zhì)是一個oc對象。
struct __block_impl impl;
// Desc:是__main_block_desc_0類型的結(jié)構(gòu)體。
struct __main_block_desc_0* Desc;
// age:是main函數(shù)中申明的局部變量age
int age;
// c++的構(gòu)造方法,類似于oc的init構(gòu)造方法
__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的結(jié)構(gòu)體內(nèi)部實現(xiàn):
struct __block_impl {
void *isa;
// 默認為0
int Flags;
int Reserved;
// FuncPtr:block內(nèi)部函數(shù)執(zhí)行地址
void *FuncPtr;
};
// Desc的結(jié)構(gòu)體內(nèi)部實現(xiàn):
static struct __main_block_desc_0 {
size_t reserved;
// Block_size:block的內(nèi)存空間大小
size_t Block_size;
}
-
block的底層結(jié)構(gòu)如右圖所示:底層結(jié)構(gòu).png
2. block的變量捕獲(capture)
為了保證block內(nèi)部能夠正常訪問外部的變量,block有個變量捕獲機制。判斷會不會被捕獲的標(biāo)準(zhǔn)是:如果是全局變量不會捕獲,如果是局部變量則會捕獲。
代碼演示如下:
// 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;
// 打印結(jié)果:age is 10, height is 20
block();
注意:self也是一個局部變量,所以也會被捕獲。所以通過self訪問的變量也都會被捕獲。方法調(diào)用中,c++底部所有的方法調(diào)用都會默認傳遞self和_cmd(方法名)兩個參數(shù),傳遞的參數(shù)就是局部變量。
3. block的類型
(1) block有3種類型,可以通過調(diào)用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型。
-
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )存放在數(shù)據(jù)區(qū)域。沒有訪問auto變量時,就是該類型block。 -
__NSStackBlock__ ( _NSConcreteStackBlock )存放在棧段。訪問了auto變量時,就是該類型block。 -
__NSMallocBlock__ ( _NSConcreteMallocBlock )存放在堆段。NSStackBlock類型block調(diào)用了copy函數(shù)時就是該類型。
存儲位置示意圖:PS:各存儲位置存儲內(nèi)容:存儲位置.png - 程序區(qū)域:程序編譯時,將代碼相關(guān)數(shù)據(jù)存儲在此區(qū)域。無需開發(fā)者管理。
- 數(shù)據(jù)區(qū)域:程序編譯時,全局變量數(shù)據(jù)存儲在此區(qū)域。無需開發(fā)者管理。
- 堆:程序運行時,動態(tài)分配內(nèi)存,需要開發(fā)者申請內(nèi)存,也需要開發(fā)者自己管理內(nèi)存。
- 棧:系統(tǒng)自動分配內(nèi)存,自己銷毀。存放局部變量數(shù)據(jù),離開作用域時內(nèi)存銷毀。

(3) block的copy操作:
- 在ARC環(huán)境下,編譯器會根據(jù)情況自動將棧上的block復(fù)制到堆上(block會變成NSMallocBlock類型),比如以下情況:
1. block作為函數(shù)返回值時;
2. 將block賦值給__strong指針時;
3. block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時;
4. block作為GCD API的方法參數(shù)時;
- ARC下block屬性的建議寫法:
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void); - MRC下block屬性的建議寫法:
@property (copy, nonatomic) void (^block)(void);
4. block內(nèi)部訪問對象類型的auto變量
當(dāng)block內(nèi)部訪問了對象類型的auto變量時:
- 如果block是在棧上,將不會對auto變量產(chǎn)生強引用。
- 如果block被拷貝到堆上:1. 會調(diào)用block內(nèi)部的copy函數(shù);2. copy函數(shù)內(nèi)部會調(diào)用
_Block_object_assign函數(shù);3._Block_object_assign函數(shù)會根據(jù)auto變量的修飾符(__strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強引用(做一次retain操作)或者弱引用; - 如果block從堆上移除。1. 會調(diào)用block內(nèi)部的
dispose函數(shù);2. dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù);3._Block_object_dispose函數(shù)會斷開對auto變量的引用(做一次release操作);函數(shù)調(diào)用時機.png
5. block關(guān)于__block修飾變量
- __block可以用于解決block內(nèi)部無法修改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代碼轉(zhuǎn)成c++底層代碼,__block int age的結(jié)構(gòu)
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
/* __block int age變量被包裝成__Block_byref_age_0類型的結(jié)構(gòu)體,結(jié)構(gòu)體里
有isa指針,實質(zhì)是oc對象。
block修改age的值是通過*age ->forwarding->age來修改的
*/
__Block_byref_age_0 *age;
};
// __Block_byref_age_0結(jié)構(gòu)體:
struct __Block_byref_age_0 {
void *__isa;
// 存放指向自己的內(nèi)存地址
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
// __block int age變量 age的值
int age;
};
-
__block修飾變量的內(nèi)存管理
當(dāng)block在棧上時,并不會對__block變量產(chǎn)生強引用
-
當(dāng)block被copy到堆時:1. 會調(diào)用block內(nèi)部的copy函數(shù);2. copy函數(shù)內(nèi)部會調(diào)用
_Block_object_assign函數(shù);3._Block_object_assign函數(shù)會對__block變量形成強引用(做一次retain操作);引用流程圖.png -
當(dāng)block從堆中移除時: 1. 會調(diào)用block內(nèi)部的
dispose函數(shù);2. dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù);3._Block_object_dispose函數(shù)會斷開對__block變量的引用(做一次release操作);移除流程圖.png
-
__block修飾的對象變量內(nèi)存管理
- 當(dāng)block在棧上時,并不會對__block變量產(chǎn)生強引用;
- 當(dāng)block被copy到堆時:1. 會調(diào)用block內(nèi)部的copy函數(shù);2.
_Block_object_assign函數(shù)會根據(jù)所指向?qū)ο蟮男揎椃?code>(__strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強引用(retain)或者弱引用(注意:這里僅限于ARC時會retain,MRC時不會retain);3._Block_object_assign函數(shù)會對__block變量形成強引用(做一次retain操作); - 當(dāng)block從堆中移除時: 1. 會調(diào)用block內(nèi)部的
dispose函數(shù);2. dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù);3._Block_object_dispose函數(shù)會斷開對__block對象變量的引用(做一次release操作);
-
__block修飾變量的__forwarding指針。
這樣做的原因是:如果block在棧上時, __forwarding指針指向是棧上的block, 如果block copy到堆上時, __forwarding指針指向的是堆上的block, 通過__forwarding指針來訪問變量,就可以保證訪問的變量是堆上的變量。流程圖如下:
上面提到,__block修飾符變量的底層是包裝成一個oc對象,其內(nèi)部有一個指向自己的__forwarding指針,訪問__block變量是通過__forwarding訪問自己內(nèi)部的__block變量。
訪問流程.png
6. block循環(huán)引用問題
- 什么是block循環(huán)引
循環(huán)引用是指對象之間的強引用鏈形成了環(huán)就創(chuàng)造了一個循環(huán)引用。最簡單的情況,兩個對象之間強引用,你引用我,我引用你,導(dǎo)致內(nèi)存無法釋放,就形成了循環(huán)引用。
block 的循環(huán)引用情況是,block 會捕獲內(nèi)部使用的對象,形成隱式的強引用,一般有以下兩種常見的情況:- 引用 self:直接寫 self:
2.成員變量:不寫 self,但實際上還是對 self 的強引用:self.callback = ^{ NSLog(@"callback: %@", self);}self.callback = ^{ NSLog(@"callback: %@", _name); // 等價于 NSLog(@"callback: %@", self->_name); } - ARC-解決循環(huán)引用問題
- 用__weak解決,不會產(chǎn)生強引用,指向的對象銷毀時,會自動讓指針置為nil。
MJPerson *person = [[MJPerson alloc] init]; __weak typeof(person) weakSelf = person; person.block = ^{ NSLog(@"age is %d", weakSelf.age); }- 用__unsafe_unretained解決,不會產(chǎn)生強引用,不安全,指向的對象銷毀時,指針存儲的地址值不變,所以一般不常用。
MJPerson *person = [[MJPerson alloc] init]; __unsafe_unretained typeof(person) weakPerson = person; person.block = ^{ NSLog(@"age is %d", weakPerson.age); };-
用__block解決(必須要調(diào)用block),缺點:1. 必須要將block強引用的對象置空,且block一定要調(diào)用;2. 一定要等到block執(zhí)行完,對象才能被釋放。如果這個block一直沒有被調(diào)用,對象就一直不會被釋放,就會存在內(nèi)存泄露。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解決








