代理設計模式對於iOS開發的人來說肯定很熟悉了,代理delegate就是委託另一個對象來幫忙完成一件事情,爲什麼要委託別人來做呢,這其實是MVC設計模式中的模塊分工問題,例如View對象它只負責顯示界面,而不需要進行數據的管理,數據的管理和邏輯是Controller的責任,所以此時View就應該將這個功能委託給Controller去實現,當然你作爲碼農強行讓View處理數據邏輯的任務,也不是不行,只是這就違背了MVC設計模式,項目小還好,隨着功能的擴展,我們就會發現越寫越難寫;還有一種情況,就是這件事情做不到,只能委託給其他對象來做了,下面的例子中我會說明這種情況。
下面的代碼我想實現一個簡單的功能,場景描述如下:TableView上面有多個CustomTableViewCell,cell上面顯示的是文字信息和一個詳情Button,點擊button以後push到一個新的頁面。爲什麼說這個場景用到了代理delegate?因爲button是在自定義的CustomTableViewCell上面,而cell沒有能力實現push的功能,因爲push到新頁面的代碼是這樣的,
[self.navigationController pushViewController...];
所以這時候CustomTableViewCell就要委託它所在的Controller去做這件事情了。
按照我的編碼習慣,我喜歡把委託的協議寫在提出委託申請的類的頭文件裏面,現在的場景中是CustomTableViewCell提出了委託申請,下面是簡單的代碼,
@protocol CustomCellDelegate <NSObject>
- (void)pushToNewPage;
@interface CustomTableViewCell : UITableViewCell
@property(nonatomic, assign) id<CustomCellDelegate> delegate;
@property (nonatomic, strong) UILabel *text1Label;
@property(nonatomic,strong) UIButton *detailBtn;
上面的代碼在CustomTableViewCell.h中定義了一個協議CustomCellDelegate,它有一個需要實現的pushToNewPage方法,然後還要寫一個屬性修飾符爲assign、名爲delegate的property,之所以使用assign是因爲這涉及到內存管理的東西,以後的博客中我會專門說明原因。
接下來在CustomTableViewCell.m中編寫Button點擊代碼,
[self.detailBtn addTarget:self action:@selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside];
對應的btnClicked方法如下,
- (void)btnClicked:(UIButton *)btn
{
if (self.delegate && [self.delegaterespondsToSelector:@selector(pushToNewPage)]) {
[self.delegate pushToNewPage];
}
}
上面代碼中的判斷條件最好是寫上,因爲這是判斷self.delegate是否爲空,以及實現CustomCellDelegate協議的Controller是否也實現了其中的pushToNewPage方法。
接下來就是受到委託申請的類,這裏是對應CustomTableViewCell所在的ViewController,它首先要實現CustomCellDelegate協議,然後要實現其中的pushToNewPage方法,還有一點不能忘記的就是要設置CustomTableViewCell對象cell的delegate等於self,很多情況下可能忘了寫cell.delegate = self;導致遇到問題不知雲裏霧裏。下面的關鍵代碼都是在ViewController.m中,
首先是服從CumtomCellDelegate協議,這個大家肯定都知道,就像很多系統的協議,例如UIAlertViewDelegate、UITextFieldDelegate、UITableViewDelegate、UITableViewDatasource一樣。
@interface ViewController ()<CustomCellDelegate>
@property (nonatomic, strong) NSArray *textArray;
然後是實現CustomCellDelegate協議中的pushToNewPage方法,
- (void)pushToNewPage
{
DetailViewController*detailVC = [[DetailViewController alloc] init];
[self.navigationController pushViewController:detailVC animated:YES];
}
還有一個步驟最容易被忘記,就是設置CumtomTableViewCell對象cell的delegate,如下代碼,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleIdentify = @"CustomCellIdentify";
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleIdentify];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleIdentify];
}
//下面代碼很關鍵
cell.delegate = self;
cell.text1Label.text = [self.textArray objectAtIndex:indexPath.row];
return cell;
}
通過cell.delegate = self;確保了CustomTableViewCell.m的判斷語句if(self.delegate && ...){}中得self.delegate不爲空,此時的self.delegate其實就是ViewController,cell對象委託了ViewController實現pushToNewPage方法。這個簡單的場景描述了使用代理的一種情況,就是CustomTableViewCell沒有能力實現pushViewController的功能,所以委託ViewController來實現。
代碼在github可以下載。
有什麼錯誤,還請大家指正。
----------------------------------------------下面是block的內容----------------------------------------------------
Block是一個C語言的特性,就像羣裏有人說的,它就是C語言的函數指針,在使用中最多的就是進行函數回調或者事件傳遞,比如發送數據到服務器,等待服務器反饋是成功還是失敗,此時block就派上用場了,這個功能的實現也可用使用代理,這麼說的話,感覺block是不是有點像代理了呢?
我之前接觸block,都是使用它作爲函數參數,當時感覺不是很理解。現在在項目中,很多時候block作爲property,這樣更加簡單直接,想想,其實property不就是定義的合成存儲的變量嘛,而block作爲函數參數也是定義的變量,所以作爲函數參數或者作爲property本質沒有區別。
看一看別人總結的block的語法吧,http://fuckingblocksyntax.com,這個鏈接亮了,fucking block syntax,操蛋的block語法啊。block有如下幾種使用情況,
1、作爲一個本地變量(local variable)
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
2、作爲@property
@property (nonatomic, copy) returnType (^blockName)(parameterTypes);
3、作爲方法的參數(method parameter)
- (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName;
4、作爲方法參數的時候被調用
[someObject someMethodThatTakesABlock: ^returnType (parameters) {...}];
5、使用typedef來定義block,可以事半功倍
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName
= ^returnType(parameters)
{...};
上面我也只是複製粘貼了一下,接下來還是實現點擊CustomTableViewCell上面的Button實現頁面跳轉的功能,我之前不止一次的類比block就像delegate,這邊我也是思維慣性,下面的內容我就當block爲代理,一些用詞描述還是跟delegate差不多。首先,在提出委託申請的CustomTableViewCell中定義block的property,
@interface CustomTableViewCell : UITableViewCell
@property (nonatomic, strong) UILabel *text1Label;
@property (nonatomic, strong) UIButton *detailBtn;
//下面的定義,請看官們對比一下
/*delegate的定義 我沒有刪除,因爲大家可以類比了看下*/
@property (nonatomic, assign) id<CustomCellDelegate> delegate;
/*這裏定義了ButtonBlock*/
@property (nonatomic, copy) void (^ButtonBlock)();
@end
這裏用copy屬性來修飾ButtonBlock property,這個原因,我會在以後的博客中作專門的解釋。
接下來在CustomTableViewCell中給它上面的detailBtn綁定點擊方法,
[self.detailBtn addTarget:self action:@selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside];
然後是btnClicked方法的細節,我把delegate的內容也沒有刪除,就是給各位比較一下block和delegate的功能和語法的相似性,
- (void)btnClicked:(UIButton *)btn
{
//這是之前的delegate
if (self.delegate && [self.delegate respondsToSelector:@selector(pushToNewPage)]) {
[self.delegate pushToNewPage];
}
//這是現在我們要說的block
if (ButtonBlock) {
ButtonBlock();
}
}
下面是一個關鍵性的地方,在ViewController2中設置其CustomTableViewCell的cell對象的ButtonBlock,也就是給它賦值,此處我還是保留了cell.delegate = self;代碼,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *blockIdentify = @"BlockIdentify";
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:blockIdentify];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:blockIdentify];
}
cell.text1Label.text = [self.textArray objectAtIndex:indexPath.row];
//delegate的不可缺少的代碼,這裏放在這兒只是爲了給各位類比一下
cell.delegate = self;
//ButtonBlock不可缺少的代碼
cell.ButtonBlock = ^{
[self pushToNewPage2];
};
return cell;
}
之所以cell.ButtonBlock = ^{};賦值,是因爲我們我們是這樣定義ButtonBlock的,void (^ButtonBLock)(),表示無返回值無參數。
然後編寫pushToNewPage2方法,
- (void)pushToNewPage2
{
DetailViewController *detailVC = [[DetailViewController alloc] init];
[self.navigationController pushViewController:detailVC animated:YES];
}
你們看這個方法是不是與CustomCellDelegate協議中的pushToNewPage方法類似。然後在回過頭來類比一樣,是不是block就是精簡版的delegate,因爲delegate設計模式要寫協議CustomCellDelegate,還有容易遺漏cell.delegate = self;但是block使用的時候就簡單多了。
如果有Block語法不懂的,可以參考fuckingblocksyntax,裏面對於Block
爲了方便對比,下面的代碼我假設是寫在ViewController子類中的
1、第一部分
定義和使用Block,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
- ( 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)定義一個有參數,沒有返回值的Block void (^printNumBlock)( int ) = ^( int num){ printf ( "int number is %d" ,num); }; |
定義Block變量,就相當於定義了一個函數。但是區別也很明顯,因爲函數肯定是在-viewDidLoad方法外面定義,而Block變量定義在了viewDidLoad方法內部。當然,我們也可以把Block定義在-viewDidLoad方法外部,例如上面的代碼塊printNumBlock的定義,就在-viewDidLoad外面。
再來看看上面代碼運行的順序問題,以第(3)個myBlock距離來說,在定義的地方,並不會執行Block{}內部的代碼,而在myBlock(3)調用之後纔會執行其中的代碼,這跟函數的理解其實差不多,就是只要在調用Block(函數)的時候纔會執行Block體內(函數體內)的代碼。所以上面的簡單代碼示例,我可以作出如下的結論,
(1)在類中,定義一個Block變量,就像定義一個函數;
(2)Block可以定義在方法內部,也可以定義在方法外部;
(3)只有調用Block時候,纔會執行其{}體內的代碼;
(PS:關於第(2)條,定義在方法外部的Block,其實就是文件級別的全局變量)
那麼在類中定義一個Block,特別是在-viewDidLoad方法體內定義一個Block到底有什麼意義呢?我表示這時候只把它當做私有函數就可以了。我之前說過,Block其實就相當於代理,那麼這時候我該怎樣將其與代理類比以瞭解呢。這時候我可以這樣說:本類中的Block就相當於類自己服從某個協議,然後讓自己代理自己去做某個事情。很拗口吧?看看下面的代碼,
1
2
3
4
5
6
7
8
9
10
|
//定義一個協議 @protocol ViewControllerDelegate<NSObject> - ( void )selfDelegateMethod; @end //本類實現這個協議ViewControllerDelegate @interface ViewController ()<ViewControllerDelegate> @property (nonatomic, assign) id<ViewControllerDelegate> delegate; @end |
接着在-viewDidLoad中的代碼如下,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
- ( 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 //實現協議中的方法 - ( void )selfDelegateMethod { NSLog(@ "自己委託自己實現的方法" ); } |
看出這種寫法的奇葩地方了嗎?自己委託自己去實現某個方法,而不是委託別的類去實現某個方法。本類中定義的一個Block其實就是閒的蛋疼,委託自己去字做某件事情,實際的意義不大,所以你很少看見別人的代碼直接在類中定義Block然後使用的,Block很多的用處是跨越兩個類來使用的,比如作爲property屬性或者作爲方法的參數,這樣就能跨越兩個類了。
2、第二部分
__block關鍵字的使用
在Block的{}體內,是不可以對外面的變量進行更改的,比如下面的語句,
1
2
3
4
5
6
7
8
9
10
|
- ( void )viewDidLoad { //將Block定義在方法內部 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關鍵字即可,如下,
1
|
__block int x = 100; |
這樣在Block的{}體內,就可以修改外部變量了。
3、第三部分:Block作爲property屬性實現頁面之間傳值
需求:在ViewController中,點擊Button,push到下一個頁面NextViewController,在NextViewController的輸入框TextField中輸入一串字符,返回的時候,在ViewController的Label上面顯示文字內容,
(1)第一種方法:首先看看通過“協議/代理”是怎麼實現兩個頁面之間傳值的吧,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//NextViewController是push進入的第二個頁面 //NextViewController.h 文件 //定義一個協議,前一個頁面ViewController要服從該協議,並且實現協議中的方法 @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文件中的內容,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//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; //設置代理 [self.navigationController pushViewController:nextVC animated:YES]; } //實現協議NextViewControllerDelegate中的方法 #pragma mark - NextViewControllerDelegate method - ( void )passTextValue:(NSString *)tfText { //self.nextVCInfoLabel是顯示NextViewController傳遞過來的字符串Label對象 self.nextVCInfoLabel.text = tfText; } |
這是通過“協議/代理”來實現的兩個頁面之間傳值的方式。
(2)第二種方法:使用Block作爲property,實現兩個頁面之間傳值,
先看看NextViewController文件中的內容,
1
2
3
4
5
6
7
8
9
10
11
12
|
//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文件中的內容,
1
2
3
4
5
6
7
8
9
10
11
12
13
|
- (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來實現兩個頁面之間傳值的目的,實際上就是取代了Delegate的功能。
另外,博客中的代碼Sample Code可以再Github下載,如果因爲Github被牆了,可以在終端使用git clone + 完整鏈接,即可克隆項目到本地。
Github中的代碼,可以開啓兩種調試模式,你需要在項目的配置文件BlockSamp-Prefix.pch中註釋或者解註釋下面的代碼,
1
|
#define Debug_BlcokPassValueEnable |
即可開啓兩種調試的方式,如果註釋了上面的語句就是使用Delegate進行調試;否則使用Block進行調試。
(1)block作爲本地變量(local variable)
returnType (^blockName)(parameterTypes) = ^returnType(parameters){...};
(2)block作爲類的成員屬性(@property)
@property (nonatomic, copy) returnType (^blockName)(parameters);
這時候可以類比delegate,實現代理功能。
(3)block作爲函數參數(method parameter)
- (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName;
調用包括block參數的函數,
[someObject somethodThatTakesABlock:^returnType(parameters){...}];
(4)使用typedef定義block類型
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters){...};
上面的內容翻譯自fuckblocksyntax,大家忘了block語法的時候可以反覆的看看。
Block的使用舉例,
(1)作爲本地變量
int (^Mutiply)(int,int) = ^(int num1,int num2){
return num1*num2;
};
block可以訪問局部變量,但是不能修改,否則會編譯報錯,
int mutiplier = 7;
int (^myBlock)(int) = ^(int num){
mutiplier++;//編譯報錯
return num*mutiplier;
};
如果要在block內部修改局部變量,則需要使用__block來修飾該局部變量,如下,
__block int mutiplier = 7;
int (^myBlock)(int) = ^(int num){
mutiplier++;//編譯不會報錯
return num*mutiplier;
};
(2)作爲函數參數使用
作爲函數的參數,block某種意義上替代了回調函數或者delegate,當函數調用的時候,假設某個事件發生,這時候block裏面的內容就會運行。這樣有利於代碼的整合和閱讀,不需要到處去實現委託方法了。
Block作爲函數參數,到底該怎麼寫代碼呢?今天(2014.05.09)晚上突然想明白,只要堅定的把Block當做delegate,一切就很簡單了,我們先看看delegate作爲參數,是怎麼寫的吧,如下代碼,
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:@"點擊Cell" delegate:self cancelButtonTitle:@"確定" otherButtonTitles:nil, nil];
[alert show];
這裏是初始化一個alert並且讓alert彈出,讓我們看看初始化的-(void)initWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles:是怎麼定義的吧,如下代碼,
- (id)initWithTitle:(NSString *)title message:(NSString *)message delegate:(id /*<UIAlertViewDelegate>*/)delegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION;//Xcode中複製過來的
我特別把delegate參數用紅色註明,所以是不是有了一點怎樣去定義block作爲參數的靈感,下面我就寫完整的代碼,分享我自己的經驗吧,
首先,還是要說一個場景,自定義的cell上面有一個Button,點擊Button,調用在ViewController中的-(void)OperateLog:方法,
不多說了,上代碼,
ImageTableViewCell.h file
typedef void (^BtnBlock)();//定義一個block
@interface ImageTableViewCell:UITableViewCell
{
BtnBlock _btnBlock;//定義一個block成員變量
}
//下面是兩個xib拖動的控件
@property (nonatomic, strong) IBOutlet UIButton *btn;
@property (nonatomic, strong) IBOutlet UIIageView *imgView;
- (void)configureCell:(UITableViewCellStyle)style Block:(BtnBlock)block reuseIdentifier:(NSString *)reuseIdentifier;
//點擊Button觸發的方法
- (IBAction)btnClicked:(id)sender;
@end
ImageTableViewCell.m file
@implementation ImageTableViewCell
//...省略無關代碼
- (void)configureCell:(UITableViewCellStyle)style Block:(BtnBlock)block reuseIdentifier:(NSString *)reuseIdentifier
{
//...省略不相干代碼
_btnBlock = block;
}
- (IBAction)btnClicked:(id)sender
{
if(_btnBlock)
{
_btnBlock();
}
}
ViewController.m file
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *simpleIdentify = @"UITableViewCellIdentify";
ImageTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleIdentify];
if (!cell) {
//將Custom.xib中的所有對象載入
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"ImageTableViewCell" owner:self options:nil];
//第一個對象就是CustomCell了
cell = [nib objectAtIndex:0];
[cell configureCell:UITableViewCellStyleDefault Block:^{
[self OperateLog];
} reuseIdentifier:simpleIdentify];
}
return cell;
}
//button點擊,最終傳遞到OperateLog方法,相當於代理方法吧
- (void)OperateLog
{
NSLog(@"點擊了cell上面的Button");
}