代碼塊的本質是和其他的變量類似,不同的是,代碼塊存儲的數據是一個函數體。使用代碼塊,你可以像調用其他標准函數一樣的調用,可以傳入參數,並得到返回值。
脫字符是代碼塊的語法標記。下圖表示代碼塊的定義。
1.代碼塊的基本使用
//無參數無返回值 void (^myblock)() = ^() { NSLog(@"Hello, World!"); }; myblock(); //帶參數無返回值 void (^myblock2)(NSString *string) = ^(NSString *string){ NSLog(@"%@",string);}; myblock2(@"Hello, World myblock2!"); //無參數有返回值 int (^myblocksss)() = ^(int i){return 12;}; int c = myblocksss(); NSLog(@"%i",c); //有參數有返回值 int (^myblock3)(int) = ^(int i){ return 12 * i; }; int i = myblock3(3); NSLog(@"%i",i);
2,利用typedef為Block進行重命名
使用typedef為block進行一次重命名,方法跟為函數指針進行重命名是一樣的:
// Copyright © 2016年 liujun. All rights reserved. // #import <Foundation/Foundation.h> typedef int (^ MyBlock)(int a,int b); int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... __block int n = 100; MyBlock block = ^(int a,int b) { n = 20; //不過沒有用__block 修飾 代碼不會編譯通過 return n + a + b; }; NSLog(@"%i %i", n ,block(3,4)); //輸出結果 100 27 NSLog(@"%i %i", block(3,4) ,n);//輸出結果 27 20 //以上輸出。說明代碼塊是在調用的時候才會被執行 NSLog(@"Hello, World!"); } return 0; }
3.Block在內存中的位置
根據Block在內存中的位置分為三種類型NSGlobalBlock,NSStackBlock, NSMallocBlock。
NSGlobalBlock:類似函數,位於text段;
NSStackBlock:位於棧內存,函數返回后Block將無效;
NSMallocBlock:位於堆內存。
// Copyright © 2016年 liujun. All rights reserved. // #import <Foundation/Foundation.h> typedef long (^Sum)(int,int); int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here...v Sum sum1 = ^ long (int a, int b) { return a + b ; }; NSLog(@"sum1 = %@", sum1);// 打印結果:sum1 = <__NSGlobalBlock__: 0x47d0> int base = 100; Sum sum2 = ^ long (int a, int b) { return base + a + b; }; NSLog(@"sum2 = %@", sum2); // 打印結果:sum2 = <__NSMallocBlock__: 0xbfffddf8> Sum sum3 = [sum2 copy]; NSLog(@"sum3 = %@", sum3); // 打印結果:sum3 = <__NSMallocBlock__: 0x902fda0> NSLog(@"Hello, World!"); } return 0; }
NSGlobalBlock,我們只要實現一個沒有對周圍變量沒有引用的Block,就會顯示為是它。而如果其中加入了對定義環境變量的引用,就是NSStackBlock。那么NSMallocBlock又是哪來的呢?malloc一詞其實大家都熟悉,就是在堆上分配動態內存時。沒錯,如果你對一個NSStackBlock對象使用了Block_copy()或者發送了copy消息,就會得到NSMallocBlock。這一段中的幾項結論可從代碼實驗得出。
也就得到了下面對block的使用注意點。
對於Global的Block,我們無需多處理,不需retain和copy,因為即使你這樣做了,似乎也不會有什么兩樣。對於Stack的Block,如果不做任何操作,就會向上面所說,隨棧幀自生自滅。而如果想讓它獲得比stack frame更久,那就調用Block_copy(),讓它搬家到堆內存上。而對於已經在堆上的block,也不要指望通過copy進行“真正的copy”,因為其引用到的變量仍然會是同一份,在這個意義上看,這里的copy和retain的作用已經非常類似。
4,外部參數在代碼塊的使用
blk1和blk2的區別在於:
blk1沒有使用Block以外的任何外部變量,Block不需要建立局部變量值的快照,這使blk1與一般函數沒有任何區別。
blk2與blk1唯一不同是的使用了局部變量base,在定義(注意是“定義”,不是“運行”)blk2時,局部變量base當前值被copy到棧上,作為常量供Block使用。執行下面代碼,結果是203,而不是204。
int base = 100; base += 100; BlkSum sum = ^ long (int a, int b) { return base + a + b; }; base++; printf("%ld",sum(1,2));
在Block內變量base是只讀的,如果想在Block內改變base的值,在定義base時要用 __block修飾:__block int base = 100;
__block int base = 100; base += 100; BlkSum sum = ^ long (int a, int b) { base += 10; return base + a + b; }; base++; printf("%ld\n",sum(1,2)); printf("%d\n",base);
輸出將是214,211。Block中使用__block修飾的變量時,將取變量此刻運行時的值,而不是定義時的快照。這個例子中,執行sum(1,2)時,base將取base++之后的值,也就是201,再執行Blockbase+=10; base+a+b,運行結果是214。執行完Block時,base已經變成211了。
static變量、全局變量 :如果把上個例子的base改成全局的、或static。Block就可以對他進行讀寫了。因為全局變量或靜態變量在內存中的地址是固定的,Block在讀取該變量值的時候是直接從其所在內存讀出,獲取到的是最新值,而不是在定義時copy的常量。
static int base = 100; BlkSum sum = ^ long (int a, int b) { base++; return base + a + b; }; base = 0; printf("%d\n", base); printf("%ld\n",sum(1,2)); // 這里輸出是3,而不是103 printf("%d\n", base);
輸出結果是0 4 1,表明Block外部對base的更新會影響Block中的base的取值,同樣Block對base的更新也會影響Block外部的base值。
Block變量,被__block修飾的變量稱作Block變量。 基本類型的Block變量等效於全局變量、或靜態變量。
5,循環引用
retain cycle問題的根源在於Block和obj可能會互相強引用,互相retain對方,這樣就導致了retain cycle,最后這個Block和obj就變成了孤島,誰也釋放不了誰。比如:
@implementation TsetBlock -(id)init{ if (self = [superinit]) { self.testStr =@"中國"; self.block = ^(NSString *name, NSString *str){ NSLog(@"arr:%@",self.testStr); // 編譯警告:Capturing 'self' strongly in this block is likely to lead to a retain cycle }; } returnself; } @end
網上大部分帖子都表述為"block里面引用了self導致循環引用",但事實真的是如此嗎?我表示懷疑,其實這種說法是不嚴謹的,不一定要顯式地出現"self"字眼才會引起循環引用。我們改一下代碼,不通過屬性self.testStr去訪問String變量,而是通過實例變量_testStr去訪問,如下:
@implementation TsetBlock -(id)init{ if (self = [superinit]) { self.testStr =@"中國"; self.block = ^(NSString *name,NSString *str){ NSLog(@"arr:%@", _testStr); // 同樣出現: Capturing 'self' strongly in this block is likely to lead to a retain cycle }; } returnself; } @end
可以發現:
即使在你的block代碼中沒有顯式地出現"self",也會出現循環引用!只要你在block里用到了self所擁有的東西!
要分兩種環境去解決:在ARC下不用__block ,而是用 __weak 為了避免出現循環引用
1.ARC:用__week
__weaktypeof (self) weakSelf = self; 或者
__weak someClass *weakSelf = self;
2.MRC:用__block ,__block修飾的變量在Block copy時是不會retain的,所以,也可以做到破解循環引用。
__block someClass *blockSelf = self;
bloack的 retain、copy、release 操作
對Block不管是retain、copy、release都不會改變引用計數retainCount,retainCount始終是1;
NSGlobalBlock:retain、copy、release操作都無效;
NSStackBlock:retain、release操作無效,必須注意的是,NSStackBlock在函數返回后,Block內存將被回收。即使retain也沒用。容易犯的錯誤是[[mutableAarry addObject:stackBlock],在函數出棧后,從mutableAarry中取到的stackBlock已經被回收,變成了野指針。正確的做法是先將stackBlock copy到堆上,然后加入數組:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock類型對象。
NSMallocBlock支持retain、release,雖然retainCount始終是1,但內存管理器中仍然會增加、減少計數。copy之后不會生成新的對象,只是增加了一次引用,類似retain;
盡量不要對Block使用retain操作。
6.代碼塊的遞歸調用
代碼塊想要遞歸調用,代碼塊變量必須是全局變量或者是靜態變量,這樣在程序啟動的時候代碼塊變量就初始化了,可以遞歸調用
static void (^ const myblock4)(int) = ^(int i) { if(i > 0) { NSLog(@"%i",i); myblock4(i - 1); } };