1.block類型-存儲代碼塊的類型
在異步編程時常需要進行函數回調,在C#中會用匿名委托或者lambda表達式講一個操作作為參數進行傳遞.
ObjC中是使用對于閉包的實現,在塊狀中我們可以持有或引用局部變量. 同時利用Block可以將一個操作作為參數進行傳遞;
blcok用法:
- 定義:返回值類型 ( ^變量名 ) ( 形參類型 );
- 賦值:變量名=^(形參){
代碼塊+形參變量
}; - 使用:變量(實參);
例:
int (^myBlcok)(int ,int)=^(int m,int n){
return m+n;
}; //無參數時大括號前()可省略
myBlock(10,5); //調用塊,省略了接受塊返回值;
總結:經過簡單了解C與OC;發現從最小的一個變量到表達式再到一個函數,其實只起兩點作用: 值(返回值) 與 功能(行為,方法,作用).
所以說一行代碼,按它是使用了值 還是 功能來解讀比較容易理解.
Block做使用場景:
- 如果回調方法比較少,1~2,最好不要超過3個,這個時候使用block比較合適
- 如果回調方法非常多,同時又不用每一個方法都必須實現,這個時候用delegate會比較方便!
block傳值的循環引用問題:
只有當block直接或間接的被self持有時,在block使用self時才需要替換為weak self。如果在 Block 內需要多次 訪問 self,則需要使用 strongSelf。
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong __typeof(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doOtherThing];
});
typedef block格式
類似函數指針,直接在定義格式之前加 typedef關鍵字,之后變量名就是類型的別名了.
typedef viod (^別名)(形參);
一般以后需要使用block作為函數方法的參數時,為方便最好用別名.而在block作用返回值時,一定需要別名,因為編譯器不能識別做此時類型做何種解釋.
延伸:經過測試,block在編譯時按代碼順序,而運行時按調用順序(變量作用域)
使用例子:
KCButton.h
#import <Foundation/Foundation.h>
@class KCButton;
typedef void(^KCButtonClick)(KCButton *);
@interface KCButton : NSObject
#pragma mark - 屬性
@property (nonatomic,copy) KCButtonClick onClick;
#pragma mark 點擊方法
-(void)click;
@end
KCButton.m
#import "KCButton.h"
@implementation KCButton
-(void)click{
NSLog(@"Invoke KCButton's click method.");
if (_onClick) {
_onClick(self);
}
}
@end
main.m
KCButton *button=[[KCButton alloc]init];
button.onClick=^(KCButton *btn){
NSLog(@"Invoke onClick method.The button is:%@.",btn);
};
[button click];
/*結果:
Invoke KCButton's click method.
Invoke onClick method.The button is:<KCButton: 0x1006011f0>.
*/
block訪問外部變量
- block內部可以訪問外部局部變量,但是此時是const copy方式,地址不同,相當于值傳遞,只讀的.如果外部定義時加前綴__block時,內部可改變外部局變值.
- block內部如果創建了和外部同名的變量,會屏蔽外部作用域.此時內部的變量也存在棧區;
原因:block本質是代碼塊,ARC下創建的時候在堆區,此時代碼只是單純儲存,沒有功能;當調用的時候,相當于代碼增加到main中,這樣代碼塊中創建的變量就跟正常的一樣; (block調用完成內部變量即釋放,而堆區的只在釋放block時一起釋放). - 如果是靜態變量(static修飾局變,生命周期延長,存儲在數據區(同初始化的全局))和全局變量.地址傳遞.此時block存儲在全局區.
- 常量字符串@"abc",加__block會引用常量變量(如:a變量,a = @"abc",內部可以任意修改a 指向的內容)的地址。不加block就是@"abc"本身地址,不可變;
三種類型block
根據block在內存中的位置
"NSGlobaBlock"類似函數,存于代碼區--全局block
"NNStackBlock"棧區,函數返回后的Block--棧
"NSMallocBlock"堆block--堆
- block內沒有使用外部變量或是只使用了全局/靜態變量時.存于全局代碼區,為全局block;---(ARC和MRC下一致)
-
當使用外部變量時
- MRC下,block代碼存于棧區;如果此外部變量A存于棧區,那么A會被copy到block分配的棧區;如果A是存于堆區,那么A在block塊內與快外相同.
- ARC下,block代碼存于堆區.如果此外部變量A存于棧區,那么A會被copy到block分配的堆區;如果A是存于堆區,那么A在block塊內與快外相同.
- 如果需要修改外部變量,需要在變量前面聲明__Block;
當使用下劃線Block修飾外部變量時:- MRC下,無論變量A存于棧還是堆區,A在block塊內與快外相同;
- ARC下,如果此外部變量A存于棧區,那么A會被轉移而不是復制到堆區;如果A是存于堆區,那么A在block塊內與快外相同.
面試題:block的@property參數(內存管理參數)為什么要用copy:如果不用copy,此時不論ARC還是MRC都是棧Bolck,棧block會提前釋放,導致無法繼續使用;可以copy到堆區手動管理內存.(而字符串copy是防止字符串如果是非常量的,外部可變,造成非預估的結果;)
block在MRC下得內存隱患(NNStacKBlock)
Block_copy將block及內部變量拷貝到堆區.
使用完畢用Blok_release(block變量)釋放此堆區空間;
block使用技巧
- block結構快速顯示:inlineBlock...(也可右下角自定義快速顯示其他格式)
- Block作為方法參數時,最好把參數列表部分加上,這樣后面調用方法時,會自動有格式;
- 做方法參數時,需要加上返回值類型;
- 做返回值時,先定義別名,最后別忘記執行返回值;
- 方法中,void(^)()表示block類型同int,做參和返回值;做實例變量
@property (nonatomic, copy) void(^變量名)() - get點語法獲取block類型實例變量時,自動執行,后面需加();