看了很多別人寫的Block的相關(guān)文章,但是別人寫的終究是別人的,看過之后沒多久也就忘了。真正的動(dòng)手敲敲,仔細(xì)考究一下,然后用自己的思路總結(jié)出來,才會(huì)轉(zhuǎn)化為自己的東西,才會(huì)牢記于心,這也是我寫這篇文章的原因。
1. Block是什么
答:是匿名函數(shù),是閉包,是對(duì)象。
但是怎么理解呢?
匿名函數(shù):是說在定義Block時(shí),其名稱可以省略不寫;
閉包:封閉的包,呵呵,看Block寫法就知道了。
還有人理解為閉包就是能夠讀取其它函數(shù)內(nèi)部變量的函數(shù)
這中說法怎么理解呢?
就是閉包允許一個(gè)函數(shù)訪問聲明該函數(shù)運(yùn)行上下文中的變量,甚至可以訪問不同運(yùn)行上文中的變量。
**用腳本語言來解釋:**
function funA(callback){
alert(callback());
}
function funB(){
var str = "Hello World"; // 函數(shù)funB的局部變量,函數(shù)funA的非局部變量
funA(
function(){
return str;
}
);
}
通過上面的代碼我們可以看出,按常規(guī)思維來說,變量str是函數(shù)funB的局部變量,作用域只在函數(shù)funB中,函數(shù)funA是無法訪問到str的。但是上述代碼示例中函數(shù)funA中的callback可以訪問到str,這是為什么呢,因?yàn)殚]包性。
來自:http://www.cocoachina.com/ios/20150109/10891.html
**用OC語言來解釋:**
#import <Cocoa/Cocoa.h>
void logBlock( int ( ^ theBlock )( void ) )
{
NSLog( @"Closure var X: %i", theBlock() );
}
int main( void )
{
NSAutoreleasePool * pool;
int ( ^ myBlock )( void );
int x;
pool = [ [ NSAutoreleasePool alloc ] init ];
x = 42;
myBlock = ^( void )
{
return x;
};
logBlock( myBlock );
[ pool release ];
return EXIT_SUCCESS;
}
上面的代碼在main函數(shù)中聲明了一個(gè)整型,并賦值42,另外還聲明了一個(gè)block,該block會(huì)將42返回。
然后將block傳遞給logBlock函數(shù),該函數(shù)會(huì)顯示出返回的值42。
即使是在函數(shù)logBlock中執(zhí)行block,而block又聲明在main函數(shù)中,但是block仍然可以訪問到x變量,并將這個(gè)值返回。
個(gè)人覺得這個(gè)例子很low,感覺只是個(gè)值傳遞的過程,并不能很好的解釋閉包。
來自:http://www.cocoachina.com/ios/20130715/6599.html
對(duì)象::我們都知道,Objective-C中的對(duì)象,其實(shí)是一個(gè)struct(結(jié)構(gòu)體),所有的對(duì)象的數(shù)據(jù)結(jié)構(gòu)里面都有一個(gè)isa指針,
那我們可以使用clang工具來看看Block的數(shù)據(jù)結(jié)構(gòu)是怎樣的:
新建一個(gè)block.m文件,里面的代碼:
int main(int argc, char * argv[]) {
void (^block)(void) = ^{
// NSLog(@"hello");
};
}
在終端使用命令clang -rewrite-objc block.m之后,會(huì)產(chǎn)生一個(gè)block.cpp文件,其中__main_block_impl_0:就是block的實(shí)現(xiàn)源碼;

可以看到Block其實(shí)也是一個(gè)struct,內(nèi)部也有一個(gè)isa指針,指向這個(gè)Block的類型:NSConcreteStackBlock。
所以block也是對(duì)象
2. Block的寫法
- 聲明一個(gè)block:
返回值(^ 名稱)(參數(shù)列表)= ^(參數(shù)列表){
//balabala
};
- block作為函數(shù)的參數(shù):
-(void)testAction:(返回值(^)(參數(shù)列表))名稱
- 使用
typedef來聲明一個(gè)全局的block變量
type 返回值 (^ 名稱)(參數(shù)列表)
3. Block的類型
我們都知道block有三種類型
__NSConcreteGlobalBlock__;
__NSConcreteStackBlock__;
__NSConcreteMallocBlock__;
怎么理解這三種類型呢?
顧名思義:
__NSConcreteGlobalBlock__://存儲(chǔ)在全局?jǐn)?shù)據(jù)區(qū)域的block,不會(huì)訪問任何外部變量
__NSConcreteStackBlock__; //存儲(chǔ)在棧上的block,出棧時(shí)會(huì)被銷毀
__NSConcreteMallocBlock__;//存儲(chǔ)在堆上的block,當(dāng)引用計(jì)數(shù)為0時(shí)會(huì)被銷毀
站在代碼表現(xiàn)形式的角度來說:
- 沒有引用外部變量的block就是
__NSConcreteGlobalBlock__; - 引用了外部變量的block就是
__NSConcreteStackBlock__; - 對(duì)
__NSConcreteStackBlock__進(jìn)行copy或Block_copy()操作后,就會(huì)變?yōu)?code>__NSConcreteMallocBlock__。
具體看代碼:
NSString * (^block1)() = ^
{
NSString *str = @"block1";
return str;
};
NSString *str = @"block1";
NSString * (^block2)() = ^
{
return str;
};
NSString * (^block3)() = Block_copy(block2);
NSString * (^block4)() = [block2 copy];
NSLog(@"block1-> %@", block1);
NSLog(@"block2-> %@", block2);
NSLog(@"block3-> %@", block3);
NSLog(@"block4-> %@", block4);
輸出日志:
**2016-06-04 10:39:14.910 BlockTest[1113:33102] block1-> <__NSGlobalBlock__: 0x5b090>**
**2016-06-04 10:39:14.910 BlockTest[1113:33102] block2-> <__NSStackBlock__: 0xbffab070>**
**2016-06-04 10:39:14.911 BlockTest[1113:33102] block3-> <__NSMallocBlock__: 0x7885c6f0>**
**2016-06-04 10:39:14.911 BlockTest[1113:33102] block4-> <__NSMallocBlock__: 0x7885c680>**
當(dāng)然,也可以用命令clang -rewrite-objc block.m生成中間層代碼,看每一個(gè)block中的isa的值也可以得到block的類型。
注:在ARC環(huán)境下,是不存在__NSConcreteStackBlock__的,因?yàn)锳RC下,編譯器會(huì)隱式的對(duì)一些變量進(jìn)行copy操作,那原先的__NSConcreteStackBlock__就會(huì)變?yōu)?code>__NSConcreteMallocBlock__。
所以ARC環(huán)境下只會(huì)存在兩種block:
__NSConcreteGlobalBlock__
__NSConcreteMallocBlock__
4. Block引用/修改外部變量
-
引用外部變量
對(duì)于 block 外的變量引用,block 默認(rèn)是將其復(fù)制到其數(shù)據(jù)結(jié)構(gòu)中來實(shí)現(xiàn)訪問的。
怎么來理解這句話呢?
還是用代碼來說話:
int a = 1;
int (^block1)() = ^()
{
int c = a;
return c;
};
a = 2;
NSLog(@"%d",block1());
輸出:
2016-06-17 14:19:45.550 Test[33356:4066610] 1
說明:
由于block1在聲明的時(shí)候,變量a的值已經(jīng)被復(fù)制到block1中使用了,所以下一行雖然變量a的值改變了,但是block1中使用的那個(gè)值是不會(huì)受到影響的。
那么引用過程到底發(fā)生了什么呢?
使用命令clang -rewrite-objc block.m生成中間層代碼:

從下往上看,最下面就是main函數(shù),
//block1的聲明
int (*block1)() = ((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
main_block_impl_0:就是block1的內(nèi)部實(shí)現(xiàn)結(jié)構(gòu);
__main_block_func_0:就是block1對(duì)應(yīng)的函數(shù)執(zhí)行方法;
其中,對(duì)變量a的使用是通過int a = __cself->a; // bound by copy來實(shí)現(xiàn)的;
main_block_desc_0:block1的描述信息,main_block_impl_0 中由于增加了一個(gè)變量a,所以結(jié)構(gòu)體的大小變大了,該結(jié)構(gòu)體大小被寫在了main_block_desc_0中,Block_size就是block1的大小。
所以block引用外部變量,其實(shí)是在自己的結(jié)構(gòu)中生成了一個(gè)相同的內(nèi)部變量,然后在block執(zhí)行的時(shí)候,將block外部變量的值賦給內(nèi)部變量。
也就是說block捕獲了這個(gè)變量(Capture local variable)
這樣就能理解,在 block 外部再去修改變量 a 的內(nèi)容,是不會(huì)影響內(nèi)部的實(shí)際變量 a 的值了。
如果此時(shí)我在block1中去修改a的值會(huì)怎樣呢?

報(bào)錯(cuò)了,需要添加__block修飾這個(gè)變量才可以,分析一下原因:
看看源碼,可以知道變量a在main函數(shù)中聲明,所以當(dāng)block1被調(diào)用后,函數(shù)__main_block_func_0已經(jīng)出了a的作用區(qū)域,顯然是無法修改a的值的。
int a = __cself->a; // bound by copy只是將a的值從main中傳到__main_block_func_0中,變量a的作用域還是沒有變。
這就就相當(dāng)于你在一個(gè)函數(shù)中去訪問另外一個(gè)函數(shù)中的局部變量,當(dāng)然時(shí)引用不到的。
當(dāng)然,也可以通過變量的指針來修改變量的值,但是當(dāng)block被執(zhí)行的時(shí)候,引用的這個(gè)變量可能已經(jīng)不在棧中了,那么此時(shí)這個(gè)變量指針就成為了野指針,此時(shí)再訪問就會(huì)crash。
等等,既然是由于作用域的問題,那么如果是全局變量的話,其作用域整個(gè)程序,不需要__block會(huì)出現(xiàn)上面的問題嗎?
int b = 1;
int main(int argc, char * argv[]) {
__block int a = 1;
void (^block2)(void) = ^()
{
a = 2;
b = 2;
};
block2();
}

可以看到函數(shù)__main_block_func_0是直接對(duì)變量b進(jìn)行賦值修改的
-
修改外部變量
上面講到對(duì)變量用__block來修飾之后,就可以在block中修改這個(gè)變量,那么這中間發(fā)生了什么呢?
對(duì)下面代碼rewrite之后,可以看到:
int main(int argc, char * argv[]) {
__block int a = 1;
void (^block2)(void) = ^()
{
a = 2;
};
block2();
}

可以看到多了一些結(jié)構(gòu):
__Block_byref_val_0:其實(shí)就是__block修飾的變量
其內(nèi)部有一個(gè)__forwarding,是指向自己的,在重新給變量a賦值的時(shí)候,其實(shí)是給__Block_byref_val_0中的a賦值。
簡單的說:
- __block修飾后的成員變量,其實(shí)是將這個(gè)成員變量包裝成了一個(gè)結(jié)構(gòu)體
__Block_byref_val_0; - 在block捕獲變量為__block修飾的外部變量時(shí),才會(huì)出現(xiàn)這兩個(gè)函數(shù):
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
3.調(diào)用__main_block_copy_0會(huì)將__block類型的成員變量從棧上復(fù)制到堆上;而當(dāng)block被釋放時(shí),相應(yīng)地會(huì)調(diào)用__main_block_dispose_0來釋放_(tái)_block類型的成員變量。
4.此時(shí),棧上和堆上都存在__block變量,即使棧上的變量作用域結(jié)束,堆上的仍然可以使用。
5.那么什么時(shí)候block會(huì)復(fù)制到堆上呢?
調(diào)用block的copy方法。
block作為函數(shù)返回值返回時(shí)。
block調(diào)用外面的_strong的id的類時(shí),或用_block時(shí)。
方法中,用usingblock或者GCD中的API時(shí)。
循環(huán)引用&內(nèi)存泄漏
了解了上述原因,也就知道了為什么會(huì)產(chǎn)生循環(huán)引用的現(xiàn)象了:
對(duì)于引用外部變量的這種情況來說,block會(huì)在內(nèi)部結(jié)構(gòu)中生成一個(gè)相同的內(nèi)部變量,那么這在MRC下就會(huì)使該變量的retaincount加1,在ARC的情況下,就會(huì)持有該變量,形成一個(gè)強(qiáng)引用的狀態(tài),那么如果這個(gè)變量是self,那么此時(shí)block和self之間就會(huì)出現(xiàn)你中有我,我中有你的情況,也就是循環(huán)引用現(xiàn)象了,如果這個(gè)變量是一個(gè)對(duì)象的話,就可能會(huì)因?yàn)闊o法釋放而出現(xiàn)內(nèi)存泄露。
那如何解決呢?
本質(zhì)上主要是破環(huán)這種你中有我,我中有你的關(guān)系,那么:
在ARC環(huán)境下,可以用__weak來修改self,產(chǎn)生一個(gè)weakself,這樣,當(dāng)block外部的self釋放后,內(nèi)部引用的weakself就回自動(dòng)釋放;
在MRC環(huán)境下,使用__block來修飾變量。
本身無法避免循環(huán)引用的問題,但是我們可以通過在 block 內(nèi)部手動(dòng)把 blockObj 賦值為 nil 的方式來避免循環(huán)引用的問題。另外一點(diǎn)就是 __block 修飾的變量在 block 內(nèi)外都是唯一的,要注意這個(gè)特性可能帶來的隱患。
文中代碼見: demo
- 問題:
為什么系統(tǒng)的block,AFN網(wǎng)絡(luò)請(qǐng)求的block內(nèi)使用self不會(huì)造成循環(huán)引用?
1. UIView的動(dòng)畫block不會(huì)造成循環(huán)引用的原因就是,這是個(gè)類方法,當(dāng)前控制器不可能強(qiáng)引用一個(gè)類,所以循環(huán)無法形成。
2. AFN無循環(huán)是因?yàn)榻^大部分情況下,你的網(wǎng)絡(luò)類對(duì)象是不會(huì)被當(dāng)前控制器引用的,這時(shí)就不會(huì)形成引用環(huán)。
AFN中的block并沒有引用控制器對(duì)象。

參考:
http://blog.csdn.net/jasonblog/article/details/7756763
http://www.cocoachina.com/ios/20130802/6725.html
http://www.cocoachina.com/ios/20150106/10850.html
http://www.cocoachina.com/ios/20160307/15441.html
http://www.itdecent.cn/p/1383d56a7ca3
http://www.itdecent.cn/p/51d04b7639f1