在 ARC 中,捕獲了外部變量的 block 的類會是 __NSMallocBlock__ 或者 __NSStackBlock__,如果 block 被賦值給了某個變量,在這個過程中會執(zhí)行 __Block__copy 將原有的 __NSStakeBlock__ 變成 __NSMallocBlock__ ;但是如果 block沒有賦值給某個變量,那他的類型就是 __NSStakeBlock__ ;沒有捕獲外部變量的 block 的類會是 __NSGlobalBlock__ 即不再對上,也不在棧上,它類似 C 語言函數(shù)一樣會在代碼段中。
在非 ARC 中,捕獲了外部變量的 block 的類會是 __NSStackBlock__ ,放在棧上,沒有捕獲外部變量的 block 時與 ARC 環(huán)境下情況相同。
block 中的 isa 指向的是該 block 的 Class 。在 block runtime 中,第一了6種類型:
_NSConcreteStackBlock ? ? 棧上創(chuàng)建的block
_NSConcreteMallocBlock ?堆上創(chuàng)建的block
_NSConcreteGlobalBlock ? 作為全局變量的block
_NSConcreteWeakBlockVariable
_NSConcreteAutoBlock
_NSConcreteFinalizingBlock
我們能接觸到的主要是前三種,后三種用于 GC 不再討論...
當 struct 第一次被創(chuàng)建時,它是存在于該函數(shù)的棧幀上的,其 Class 是固定的 __NSStackBlock 。其捕獲的變量是會賦值到結構體的成員上,所以當 block 初始完成后,捕獲到的變量不能更改。?
當函數(shù)返回時,函數(shù)的棧幀被銷毀,這個 block 的內存也會被清除。所以在函數(shù)結束后,如果仍需要這個 block ,就需要用到 Block_copy() 方法將它拷貝到堆上。這個方法的核心動作是:申請內存,將棧數(shù)據復制過去,將 Class 改一下,最后向捕獲到的對象發(fā)送 retain ,增加 block 的引用計數(shù)。
1.全局 block?
定義在函數(shù)外部的 block 是 global 類型的
定義在函數(shù)內部的 block ,但是沒有捕獲任何自動變量,那也是全局的。
全局靜態(tài) block,不會訪問任何外部變量,執(zhí)行完就銷毀。
typedef
int(^blk_t)(int);
for(...){
blk_t blk = ^(intcount) {returncount;};
}或
^{ NSLog(@"Hello World!"); }();
2.棧 block
保存在棧中的 block,當函數(shù)返回時會被銷毀,和第一種的區(qū)別就是調用了外部變量。
typedef void (^block_t)() ;
-(block_t)returnBlock{
? ? __block int add=10;
? ? return ^{printf("add=%d\n",++add);};
}
這是因為:block捕獲了棧上的add自動變量,此時add已經變成了一個結構體,而block中擁有這個結構體的指針。即如果返回block的話就是返回局部變量的指針。而這一點恰是編譯器已經斷定了。在ARC下可以編譯過,是因為ARC使用了autorelease了。
再說一個場景:
- (block_t) returnBlock{
? ? __block int add=10;
? ? block_t blk_h =^{printf("add=%d\n",++add);};
? ? return blk_h;
}
block_t bb = [self returnBlock];
bb();
這段代碼,只是使用了一個自動block變量,可以編過,但是造成程序崩潰了。
如果在返回block的時候加上copy,可以輸出正確的數(shù)值11
或
[UIView animateWithDuration:3 animations:^{ self.view.backgroundColor = [UIColor redColor]; }];
3.堆內存
保存在堆中的 block,當引用計數(shù)為 0 時會被銷毀。例如按鈕的點擊事件,一直存在,即使執(zhí)行過,也不銷毀,因為按鈕還可能被點擊。直到持有按鈕的View被銷毀,它才會被銷毀。
有時候我們需要調用 block 的 copy 函數(shù),將 block 拷貝到堆上。如下代碼:
- (id) getBlockArray{
? ? int val =10;
? ? return [NSArray arrayWithObjects:
? ? ? ? ^{NSLog(@"blk0:%d",val);},
? ? ? ? ^{NSLog(@"blk1:%d",val);},nil];
}
id obj = getBlockArray();
typedef void (^blk_t)(void);
blk_t blk = (blk_t){obj objectAtIndex:0};
blk();
這段代碼在最后一行 blk() 會異常,因為數(shù)組中的block是棧上的。因為val是棧上的。解決辦法就是調用copy方法。
這種場景,ARC 也不會為你添加 copy,因為ARC不確定,采取了保守的措施:不添加copy。所以ARC下也是會異常退出。
Block 在棧和堆中的區(qū)別
1.默認情況下,block 默認存儲在棧中;如果將 block 進行一次 copy 操作,block 會轉移到堆中。
2.如果 block 在棧中,block 訪問了外界變量,那么不會對外界進行 retain 操作。
3.在 property 中使用 block 時,一般都是用 copy 修飾 block ,將 block 拷貝到堆上,減少無意中的堆 block 釋放。
Copy 的作用
1. 防止外界修改內部數(shù)據
2.可以使用 copy 保存 block ,這樣可以保住 block 中使用外界對象的生命。
Block的聲明語法
一、block的聲明
int multiplier = 10;
int (^MyBlock) (int) = ^(int num) { return num*multiplier };
int Myblock 是塊對象,返回整形值
"^" 符號將 MyBlock 聲明為一個塊對象
(int) 它有一個參數(shù),參數(shù)的類型也是整形
^(int num) 參數(shù)的名稱是num
^(int num) { return num*multiplier } 這是定義塊對象的語法結構,這部分就是賦給MyBlock變量的值。
{ return num*multiplier?} 這是塊對象的主體部分
1.寫在方法里作為局部變量
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
returnType是返回值
blockName是block名稱
parameterTypes是參數(shù)
//無參數(shù)無返回值
void?(^myblock)() = ^()
{
????NSLog(@"Hello, World!");
};
myblock();
//帶參數(shù)無返回值
void?(^myblock2)(NSString?*string) = ^(NSString?*string){?NSLog(@"%@",string);};
myblock2(@"Hello, World myblock2!");
?//無參數(shù)有返回值
int?(^myblocksss)() = ^(int?i){return?12;};
int?c = myblocksss();
NSLog(@"%i",c);
//有參數(shù)有返回值
int?(^myblock3)(int) = ^(int?i){?return?12 * i; };
int?i = myblock3(3);
NSLog(@"%i",i);
2.作為類的屬性
@property(nonatomic,copy) returnType (^blockName)(parameterTypes)
returnType是返回值
blockName是block名稱
parameterTypes是參數(shù)
3.作為方法參數(shù)
- (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName;
returnType是返回值
blockName是block名稱
parameterTypes是參數(shù)
4.調用方法是傳入的參數(shù)
[self someMethodThatTakesABlock:^returnType (parameters) {...}];
returnType是返回值
blockName是block名稱
parameterTypes是參數(shù)
5.自定義Block類型
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};
returnType是返回值
blockName是block名稱
parameterTypes是參數(shù)