一殿如、協(xié)議和代理
首先如果要學(xué)習(xí)協(xié)議和代理,要先了解一下什么是協(xié)議和代理
協(xié)議(protocol)
協(xié)議就是定義一個(gè)需要完成任務(wù)(函數(shù))的公用接口最爬,因?yàn)镺bjective - C語言沒有多繼承涉馁,所以很多時(shí)候都是用Protocol(協(xié)議)來代替。比如:你要寫一個(gè)程序里面包括優(yōu)秀學(xué)生爱致,優(yōu)秀三好學(xué)生烤送,普通學(xué)生三個(gè)類。他們都應(yīng)該繼承學(xué)生類糠悯,但是優(yōu)秀學(xué)生和優(yōu)秀三好學(xué)生 都有一個(gè)相似的部分都是優(yōu)秀學(xué)生帮坚,但是因?yàn)镺bjective - C 沒有多繼承,所以就會(huì)用到Protocol(協(xié)議)來定義一套公用的接口互艾,代替多繼承试和。當(dāng)然協(xié)議還有另一種用法,接下來我們將會(huì)講到
定義一套公用的接口(Public)
@required:必須實(shí)現(xiàn)的方法忘朝,默認(rèn)在@protocol里的方法都要求實(shí)現(xiàn)灰署。
@optional:可選實(shí)現(xiàn)的方法(可以全部都不實(shí)現(xiàn))
比如常用的 UITableViewDelegate 就是一個(gè)協(xié)議判帮,遵守這個(gè)協(xié)議必須實(shí)現(xiàn)UI TabViewDelegate的方法
代理
委托代理是指給一個(gè)對象提供一個(gè)機(jī)會(huì)局嘁,對另一個(gè)對象中的變化做出反應(yīng)溉箕,或者響應(yīng)另一個(gè)對象的行為。它的基本思想是兩個(gè)對象協(xié)同解決問題悦昵。
一般在代碼開發(fā)過程中肴茄,View層一本不會(huì)直接操作函數(shù),但會(huì)接受一些時(shí)間但指,比如點(diǎn)擊事件寡痰,這種事件需要拿到controller里去處理,而完成這個(gè)讓Controller響應(yīng)View事件的操作就可以用代理來完成棋凳。
一般代理和協(xié)議都是聯(lián)合使用的:代理讓‘別人去替你做事情 ’拦坠,‘事情’用協(xié)議聲明
接下來講解一下代理的用法:
先聲明一個(gè)協(xié)議(事情)
@protocol ProtocolDelegate <NSObject>
// 必須實(shí)現(xiàn)的方法
@required
- (void)error;
// 可選實(shí)現(xiàn)的方法
@optional
- (void)other;
- (void)other2;
- (void)other3;
@end
@interface ViewControllerB : UIViewController
// 委托代理人,代理一般需使用弱引用(weak)
@property (weak, nonatomic) id<ProtocolDelegate> delegate;
@end
接下來剩岳,作為當(dāng)事人贞滨,在讓別人去替你做事的時(shí)候,你需要告訴別人什么時(shí)候做:
// 在ViewControllerB的.m文件里
- (void)backAction:(id)sender
{
// 協(xié)議是否響應(yīng)了error方法 (看看代理人是不是準(zhǔn)備好要做事情了)
if ([_delegate respondsToSelector:@selector(error)]) {
[_delegate error]; // 告訴別人 你可以做事情了
}}
@end
然后代理人需要做什么準(zhǔn)備呢拍棕?
#import "ViewController.h"
#import "ViewControllerB.h"
@interface ViewController () <ProtocolDelegate> // 聲明協(xié)議(如果你想替別人做事晓铆,需要先遵守這個(gè)合同)
@end
@implementation ViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
ViewControllerB *vc = segue.destinationViewController;
[vc setDelegate:self]; // 確定自己(ViewControllerB)的代理人(self / ViewController);
}
// 當(dāng)自己自己(ViewControllerB)需要實(shí)現(xiàn)error方法時(shí) 調(diào)用此方法
- (void)error
{
}
@end
二绰播、block:
block的代理的要完成目標(biāo)相似骄噪,都是讓別人去替完成任務(wù)。那就直接上代碼
//BViewController.h
#import <UIKit/UIKit.h>
typedef void(^CallBackBlcok) (NSString *text);//1
@interface BViewController : UIViewController
// 在自己頁面聲明一個(gè)block
@property (nonatomic,copy) CallBackBlcok callBackBlock;//2
@end
在這里蠢箩,代碼 1 用 typedef 定義了void(^) (NSString *text)的別名為 CallBackBlcok链蕊。這樣我們就可以在代碼 2 中,使用這個(gè)別名定義一個(gè) Block 類型的變量callBackBlock谬泌。
在定義了callBackBlock之后示弓,我們可以在 B 中的點(diǎn)擊事件中添加callBackBlock的傳參操作
//BViewController.m
// 然后在.m 決定什么時(shí)候替你完成‘事’
- (IBAction)click:(id)sender {
self.callBackBlock(_textField.text); //1
}
這樣我們就可以在想要獲取數(shù)據(jù)回調(diào)的地方,也就 A 的視圖中調(diào)用 block:
// AViewController.m
- (IBAction)push:(id)sender {
BViewController *bVC = [self.storyboard instantiateViewControllerWithIdentifier:@"BViewController"];
bVC.callBackBlock = ^(NSString *text){ // 1
NSLog(@"text is %@",text);
self.label.text = text;
};
[self.navigationController pushViewController:bVC animated:YES];
}
代碼 1 中呵萨,通過對回調(diào)將 B 中的數(shù)據(jù)傳遞到代碼塊中奏属,并賦值給 A中的 label,實(shí)現(xiàn)了整個(gè)回調(diào)過程潮峦。
上例是通過將 block 直接賦值給 block 屬性囱皿,也可以通過方法參數(shù)的方式傳遞 block 塊。
不過上面的代碼有問題:
有人問了忱嘹,兄弟嘱腥,有錯(cuò)誤的代碼還粘?我要回答了拘悦,兄弟 我之前也是被這個(gè)代碼騙慘了齿兔,拿出來就是讓大家以你想讓大家深刻記住block的缺點(diǎn),就是block會(huì)循環(huán)引用。
把代碼中的self改成弱引用就可以了:
__weak AViewController *weakSelf = self;
bVC.callBackBlock = ^(NSString *text){
NSLog(@"text is %@",text);
// self.label.text = text;
weakSelf.label.text = text;
};
因?yàn)樯厦娴拇a self.label.text = text;分苇,在 Block 中引用 self 添诉,也就是 A ,而 A 創(chuàng)建并引用了 B 医寿,而 B 引用callBackBlock栏赴,此時(shí)就形成了一個(gè)循環(huán)引用。改成弱引用就好了原因靖秩。
協(xié)議须眷、代理與block基本就到這了
三、區(qū)別
很多同學(xué)可能還會(huì)迷糊代理與block沟突,兩者實(shí)現(xiàn)的功能差不多那么區(qū)別是什么呢花颗?接下來我給把兩者區(qū)別列出來一下:
block 和 delegate 都可以通知外面。block 更輕型惠拭,使用更簡單捎稚,能夠直接訪問上下文,這樣類中不需要存儲(chǔ)臨時(shí)數(shù)據(jù)求橄,使用 block 的代碼通常會(huì)在同一個(gè)地方今野,這樣讀代碼也連貫。delegate 更重一些罐农,需要實(shí)現(xiàn)接口条霜,它的方法分離開來,很多時(shí)候需要存儲(chǔ)一些臨時(shí)數(shù)據(jù)涵亏,另外相關(guān)的代碼會(huì)被分離到各處宰睡,沒有 block 好讀。
應(yīng)該優(yōu)先使用 block气筋。而有兩個(gè)情況可以考慮 delegate拆内。
1.有多個(gè)相關(guān)方法。假如每個(gè)方法都設(shè)置一個(gè) block, 這樣會(huì)更麻煩宠默。而 delegate 讓多個(gè)方法分成一組麸恍,只需要設(shè)置一次,就可以多次回調(diào)。當(dāng)多于 3 個(gè)方法時(shí)就應(yīng)該優(yōu)先采用 delegate。
比如一個(gè)網(wǎng)絡(luò)類洋闽,假如只有成功和失敗兩種情況,每個(gè)方法可以設(shè)計(jì)成單獨(dú) block融欧。但假如存在多個(gè)方法,比如有成功卦羡、失敗噪馏、緩存麦到、https 驗(yàn)證,網(wǎng)絡(luò)進(jìn)度等等欠肾,這種情況下瓶颠,delegate 就要比 block 要好。
在 swift 中董济,利用 enum, 多個(gè)方法也可以合并成一個(gè) block 接口。swift 中的枚舉根據(jù)情況不同要门,可以關(guān)聯(lián)不同數(shù)據(jù)類型虏肾。而在 objc 就不建議這樣做,objc 這種情況下欢搜,額外數(shù)據(jù)需要使用 NSObject 或者 字典進(jìn)行強(qiáng)轉(zhuǎn)封豪,接口就不夠安全。
2.為了避免循環(huán)引用炒瘟,也可以使用 delegate吹埠。使用 block 時(shí)稍微不注意就形成循環(huán)引用,導(dǎo)致對象釋放不了疮装。這種循環(huán)引用缘琅,一旦出現(xiàn)就比較難檢查出來。而 delegate 的方法是分離開的廓推,并不會(huì)引用上下文刷袍,因此會(huì)更安全些。
假如寫一個(gè)庫供他人使用樊展,不清楚使用者的水平如何呻纹。這時(shí)為防止誤用,寧愿麻煩一些专缠,笨一些雷酪,使用 delegate 來替代 block。
將 block 簡單分類涝婉,有三種情形哥力。
- 臨時(shí)性的,只用在棧當(dāng)中墩弯,不會(huì)存儲(chǔ)起來省骂。
比如數(shù)組的 foreach 遍歷,這個(gè)遍歷用到的 block 是臨時(shí)的最住,不會(huì)存儲(chǔ)起來钞澳。
- 需要存儲(chǔ)起來,但只會(huì)調(diào)用一次涨缚,或者有一個(gè)完成時(shí)期轧粟。
比如一個(gè) UIView 的動(dòng)畫策治,動(dòng)畫完成之后,需要使用 block 通知外面兰吟,一旦調(diào)用 block 之后通惫,這個(gè) block 就可以刪掉。
- 需要存儲(chǔ)起來混蔼,可能會(huì)調(diào)用多次履腋。
比如按鈕的點(diǎn)擊事件,假如采用 block 實(shí)現(xiàn)惭嚣,這種 block 就需要長期存儲(chǔ)遵湖,并且會(huì)調(diào)用多次。調(diào)用之后晚吞,block 也不可以刪除延旧,可能還有下一次按鈕的點(diǎn)擊。
對于臨時(shí)性的槽地,只在棧中使用的 block, 沒有循環(huán)引用問題迁沫,block 會(huì)自動(dòng)釋放。而只調(diào)用一次的 block捌蚊,需要看內(nèi)部的實(shí)現(xiàn)集畅,正確的實(shí)現(xiàn)應(yīng)該是 block 調(diào)用之后,馬上賦值為空缅糟,這樣 block 也會(huì)釋放牡整,同樣不會(huì)循環(huán)引用。
而多次調(diào)用時(shí)溺拱,block 需要長期存儲(chǔ)逃贝,就很容易出現(xiàn)循環(huán)引用問題。
Cocoa 中的 API 設(shè)計(jì)也是這樣的迫摔,臨時(shí)性的沐扳,只會(huì)調(diào)用一次的,采用 block句占。而多次調(diào)用的沪摄,并不會(huì)使用 block。比如按鈕事件纱烘,就使用 target-action杨拐。有些庫將按鈕事件從 target-action 封裝成 block 接口, 反而容易出問題。