本文轉(zhuǎn)載自:http://my.oschina.net/leejan97/blog/268536
本文翻譯自蘋果的文檔,有刪減,也有添加自己的理解部分。
如果有Block語法不懂的,可以參考fuckingblocksyntax。
為了方便對比,下面的代碼我假設(shè)是寫在ViewController子類中的。
1、定義和使用Block
- (void)viewDidLoad
{
[super viewDidLoad];
//(1)定義無參無返回值的Block
void (^printBlock)() = ^(){
printf("no number");
};
printBlock();
printBlock(9);
int mutiplier = 7;
//(3)定義名為myBlock的代碼塊,返回值類型為int
int (^myBlock)(int) = ^(int num){
return num*mutiplier;
}
//使用定義的myBlock
int newMutiplier = myBlock(3);
printf("newMutiplier is %d",myBlock(3));
}
//定義在-viewDidLoad方法外部
//(2)定義一個有參數(shù),沒有返回值的Block
void (^printNumBlock)(int) = ^(int num){
printf("int number is %d",num);
};
定義Block變量,就相當于定義了一個函數(shù)。但是區(qū)別也很明顯,因為函數(shù)肯定是在-viewDidLoad方法外面定義,而Block變量定義在了viewDidLoad方法內(nèi)部。當然,我們也可以把Block定義在-viewDidLoad方法外部,例如上面的代碼塊printNumBlock的定義,就在-viewDidLoad外面。
再來看看上面代碼運行的順序問題,以第(3)個myBlock距離來說,在定義的地方,并不會執(zhí)行Block{}內(nèi)部的代碼,而在myBlock(3)調(diào)用之后才會執(zhí)行其中的代碼,這跟函數(shù)的理解其實差不多,就是只要在調(diào)用Block(函數(shù))的時候才會執(zhí)行Block體內(nèi)(函數(shù)體內(nèi))的代碼。所以上面的簡單代碼示例,我可以作出如下的結(jié)論:
- 在類中,定義一個Block變量,就像定義一個函數(shù);
- Block可以定義在方法內(nèi)部,也可以定義在方法外部。
- 只有調(diào)用Block時候,才會執(zhí)行其{ }體內(nèi)的代碼;(PS:關(guān)于第(2)條,定義在方法外部的Block,其實就是文件級別的全局變量)
那么在類中定義一個Block,特別是在-viewDidLoad方法體內(nèi)定義一個Block到底有什么意義呢?我表示這時候只把它當做私有函數(shù)就可以了。我之前說過,Block其實就相當于代理,那么這時候我該怎樣將其與代理類比以了解呢。這時候我可以這樣說:本類中的Block就相當于類自己服從某個協(xié)議,然后讓自己代理自己去做某個事情。很拗口吧?看看下面的代碼:
//定義一個協(xié)議
@protocol ViewControllerDelegate<NSObject>
- (void)selfDelegateMethod;
@end
//本類實現(xiàn)這個協(xié)議ViewControllerDelegate
@interface ViewController ()<ViewControllerDelegate>
@property (nonatomic, assign) id<ViewControllerDelegate> delegate;
@end
接著在-viewDidLoad中的代碼如下:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.delegate = self;
if (self.delegate && [self.delegate respondsToSelector:@selector(selfDelegateMethod)]) {
[self.delegate selfDelegateMethod];
}
}
#pragma mark - ViewControllerDelegate method
//實現(xiàn)協(xié)議中的方法
- (void)selfDelegateMethod
{
NSLog(@"自己委托自己實現(xiàn)的方法");
}
看出這種寫法的奇葩地方了嗎?自己委托自己去實現(xiàn)某個方法,而不是委托別的類去實現(xiàn)某個方法。本類中定義的一個Block其實就是閑的蛋疼,委托自己去字做某件事情,實際的意義不大,所以你很少看見別人的代碼直接在類中定義Block然后使用的,Block很多的用處是跨越兩個類來使用的,比如作為property屬性或者作為方法的參數(shù),這樣就能跨越兩個類了。
2、__block關(guān)鍵字的使用
在Block的{ }體內(nèi),是不可以對外面的變量進行更改的,比如下面的語句:
- (void)viewDidLoad
{
//將Block定義在方法內(nèi)部
int x = 100;
void (^sumXAndYBlock)(int) = ^(int y){
x = x+y;
printf("new x value is %d",x);
};
sumXAndYBlock(50);
}
這段代碼有什么問題呢,Xcode會提示x變量錯誤信息:Variable is not assigning (missing __block type),這時候給int x = 100;語句前面加上__block關(guān)鍵字即可(注意前面有兩個短杠),如下:
__block int x = 100;
這樣在Block的{ }體內(nèi),就可以修改外部變量了。
3、第三部分:Block作為property屬性實現(xiàn)頁面之間傳值
需求:在ViewController中,點擊Button,push到下一個頁面NextViewController,在NextViewController的輸入框TextField中輸入一串字符,返回的時候,在ViewController的Label上面顯示文字內(nèi)容。
(1)第一種方法:首先看看通過“協(xié)議/代理”是怎么實現(xiàn)兩個頁面之間傳值的吧
//NextViewController是push進入的第二個頁面
//NextViewController.h 文件
//定義一個協(xié)議,前一個頁面ViewController要服從該協(xié)議,并且實現(xiàn)協(xié)議中的方法
@protocol NextViewControllerDelegate <NSObject>
- (void)passTextValue:(NSString *)tfText;
@end
@interface NextViewController : UIViewController
@property (nonatomic, assign) id<NextViewControllerDelegate> delegate;
@end
//NextViewController.m 文件
//點擊Button返回前一個ViewController頁面
- (IBAction)popBtnClicked:(id)sender {
if (self.delegate && [self.delegate respondsToSelector:@selector(passTextValue:)]) {
//self.inputTF是該頁面中的TextField輸入框
[self.delegate passTextValue:self.inputTF.text];
}
[self.navigationController popViewControllerAnimated:YES];
}
接下來我們在看看ViewController文件中的內(nèi)容:
//ViewController.m 文件
@interface ViewController ()<NextViewControllerDelegate>
@property (strong, nonatomic) IBOutlet UILabel *nextVCInfoLabel;
@end
//點擊Button進入下一個NextViewController頁面
- (IBAction)btnClicked:(id)sender
{
NextViewController *nextVC = [[NextViewController alloc] initWithNibName:@"NextViewController" bundle:nil];
nextVC.delegate = self;//設(shè)置代理
[self.navigationController pushViewController:nextVC animated:YES];
}
//實現(xiàn)協(xié)議NextViewControllerDelegate中的方法
#pragma mark - NextViewControllerDelegate method
- (void)passTextValue:(NSString *)tfText
{
//self.nextVCInfoLabel是顯示NextViewController傳遞過來的字符串Label對象
self.nextVCInfoLabel.text = tfText;
}
這是通過“協(xié)議/代理”來實現(xiàn)的兩個頁面之間傳值的方式。
(2)第二種方法:使用Block作為property,實現(xiàn)兩個頁面之間傳值
先看看NextViewController文件中的內(nèi)容:
//NextViewController.h 文件
@interface NextViewController : UIViewController
@property (nonatomic, copy) void (^NextViewControllerBlock)(NSString *tfText);
@end
//NextViewContorller.m 文件
- (IBAction)popBtnClicked:(id)sender {
if (self.NextViewControllerBlock) {
self.NextViewControllerBlock(self.inputTF.text);
}
[self.navigationController popViewControllerAnimated:YES];
}
再來看看ViewController文件中的內(nèi)容:
- (IBAction)btnClicked:(id)sender
{
NextViewController *nextVC = [[NextViewController alloc] initWithNibName:@"NextViewController" bundle:nil];
nextVC.NextViewControllerBlock = ^(NSString *tfText){
[self resetLabel:tfText];
};
[self.navigationController pushViewController:nextVC animated:YES];
}
#pragma mark - NextViewControllerBlock method
- (void)resetLabel:(NSString *)textStr
{
self.nextVCInfoLabel.text = textStr;
}
好了就這么多代碼,可以使用Block來實現(xiàn)兩個頁面之間傳值的目的,實際上就是取代了Delegate的功能。
另外,博客中的代碼Sample Code可以在Github下載,如果因為Github被墻了,可以在終端使用git clone + 完整鏈接,即可克隆項目到本地。
Github中的代碼,可以開啟兩種調(diào)試模式,你需要在項目的配置文件BlockSamp-Prefix.pch中注釋或者解注釋下面的代碼:
#define Debug_BlcokPassValueEnable
即可開啟兩種調(diào)試的方式,如果注釋了上面的語句就是使用Delegate進行調(diào)試;否則使用Block進行調(diào)試。