void(^myBlock)(void) = ^() { NSLog(@"定義了一個 Block"); }; [UIView animateWithDuration:0.3 animations:myBlock];
那么為什么需要把 block 當作參數去使用呢?
這就引出了 block 這個時候的使用場景:當自己封裝一個類的時候,有些事情由外部決定,但什么時候做由內部決定,(即內部決定執行時間,外部傳入具體做些什么)——這個時候就可以使用 block 來作為參數。
- (void(^)(void))test { return ^{ NSLog(@"test"); }; }
那么上面這個方法該如何調用呢?
第一種常規方式: [self test](); ,因為 test 是一個方法,所以 [self test] 調用方法后得到的是一個 block,而執行 block 只要在它后面加 () 就可以。所以 [self test](); 打印出了 test。
第二種方式: self.test(); 這樣類似於屬性的 get 方法,后面加一個 () 就相當於執行 block。這里可以留存一個疑問:oc 的 get 方法其實本質是 Xcode 的編譯器特性,使用點語法可以對應到屬性的 get 方法。那為什么方法返回值為 block 時使用點語法就能被調用到了呢?(待深究)
#import <Foundation/Foundation.h> @interface AddTool : NSObject - (AddTool *(^)(NSInteger))add; @property (nonatomic, assign) NSInteger result; @end
.m 文件
#import "AddTool.h" @implementation AddTool - (AddTool *(^)(NSInteger))add { return ^(NSInteger value) { _result += value; return self; }; } @end
鏈式調用如下:
AddTool *addTool = [AddTool new]; NSInteger result = addTool.add(5).add(3).add(1).result; // NSLog: 9
【Block 逆傳值】
這里用控制器跳轉舉例 block 的逆傳值。A push 到 B 之后,B 傳值到 A 中。可以在 B 頭文件聲明一個 block 屬性,在 .m 文件中執行這個 block。
B .h 文件:
@interface BViewController : UIViewController @property (nonatomic, strong) void(^callBack)(NSString *str); @end
B .m 文件
- (void)viewDidLoad { [super viewDidLoad]; self.callBack(@"123"); }
在 A 中 push 處的代碼就可以收到 B 傳過來的值。
BViewController *bVC = [[BViewController alloc] init]; bVC.callBack = ^(NSString *str) { NSLog(@"%@", str); // NSLog: 123 }; [self.navigationController pushViewController:bVC animated:YES];
【Block的循環引用】
循環引用的描述:Block的擁有者在Block作用域內部又引用了自己,因此導致了Block的擁有者永遠無法釋放內存,就出現了循環引用的內存泄漏。
發生的場合:ARC 中 block 為 strong 或 copy 屬性,在Block內部使用了當前類的self屬性,同時這個類包含了別一個類的 Block 屬性。

- 全局區和靜態區: 它們其實是一樣的,從內存上來看,全局變量和靜態變量都是保存在靜態存儲區,生命期和程序一樣,都是在靜態數據區分配內存。在程序結束后回收內存。
- 堆: 手動管理內存
- 棧: 代碼塊一過,系統自動回收對應內存區

2. MRC 下 Blcok 內存分配
在 ARC 之前,Block 的內存管理需要區分是 Global (全局)、Stack (棧) 還是 Heap (堆)。
- 全局區: 如下定義一個Block,並將它使用, 它的打印結果為 <__NSGobalBlock__: 0x1075c076> ,global 表示全局區。全局區表示到處都可以使用。(ps:假如block內部訪問static修辭的外部局變量,那么它也是在全局區,關於靜態區與全局剛剛上面已經解釋過)
void(^block)() = ^{ }; NSLog(@"%@", block);
- 棧區:如下當Block內訪問一個外面的局部變量a,它的打印結果為 <__NSStockBlock__: 0x7fff5baa09d8> ,stack表示棧區
int a = 0; // block 存放在全局區 void(^block)() = ^{ NSLog(@"%d", a); };
- 堆區:使用 copy 進行強引用時 block 會 copy 一份到堆區
在 MRC 環境下用 retain 修辭 Block 會有黃色警告,如下圖,警告提示最好使用 copy

int a = 0; void(^block)() = ^{ NSLog(@"調用block"); NSLog(@"%d", a); }; self.block = block; // 報錯
首先,全局的 Block 比較簡單,一句話就可以講完:凡是沒有引用到 Block 作用域外面的參數的 Block 都會放到全局內存塊中,在全局內存塊的 Block 不用考慮內存管理問題。(放在全局內存塊是為了在之后再次調用該 Block 時能快速反應,當然沒有調用外部參數的 Block 根本不會出現內存管理問題)。
所以 Block 的內存管理出現問題的,絕大部分都是在堆內存中的 Block 出現了問題。實際上屬於 Block 特有的內存管理問題就只有一個:循環引用。
- 全局區:還是如下代碼,默認 Block 還是放在全局區,沒訪問外部變量就都是在全局區,與是否ARC無關
void(^block)() = ^{ }; NSLog(@"%@", block);
- 堆區: 訪問一個外部局部變量或對象時,代碼如下,它的打印結果 <__NSMallocBlock__: 0x7f9f5baa09d8> ,這個 malloc 分配的內存是表示在堆上。(這有點像ARC下默認一個對象就是強引用,好處是不會一創建就銷毀。)
int a = 0; void(^block)() = ^{ NSLog(@"%d", a); }; NSLog(@"%@", block);
int a = 5; a = 10; void(^block)() = ^{ NSLog(@"%d", a); // NSLog: 10 }; block();
1. 值傳遞情況
再看如下代碼,這個打印出來是5,雖然 block() 執行之前 a = 10,但因為 a = 5 之后就立即被傳遞進了 block,只是還沒有執行而已,即內存已配好。而且這時的 block 是值傳遞,這時外面更改無法改變里面的值。(如果是指針傳遞則外面可以更改里面的值)
int a = 5; // 值傳遞 void(^block)() = ^{ NSLog(@"%d", a); // NSLog: 5 }; a = 10; block();
static int a = 5; // 指針傳遞 void(^block)() = ^{ NSLog(@"%d", a); // NSLog: 10 }; a = 10; block();
下在情況也是指針傳遞,所以打印出來也會是10
__block int a = 5; // 指針傳遞 void(^block)() = ^{ NSLog(@"%d", a); // NSLog: 10 }; a = 10; block();
block變量傳遞總結:如果在block內部訪問的是局部變量,那么就是值傳遞,否則就是指針傳遞。