轉(zhuǎn)自 <簡書 — 劉小壯>
代理的基本使用
代理是一種通用的設(shè)計(jì)模式检吆,在iOS
中對(duì)代理設(shè)計(jì)模式支持的很好出爹,有特定的語法來實(shí)現(xiàn)代理模式,OC語言可以通過@Protocol
實(shí)現(xiàn)協(xié)議。
代理主要由三部分組成:
- 協(xié)議:用來指定代理雙方可以做什么壹瘟,必須做什么其障。
- 代理:根據(jù)指定的協(xié)議银室,完成委托方需要實(shí)現(xiàn)的功能。
- 委托:根據(jù)指定的協(xié)議励翼,指定代理去完成什么功能蜈敢。
這里用一張圖來闡述一下三方之間的關(guān)系:
Protocol-協(xié)議的概念
從上圖中我們可以看到三方之間的關(guān)系,在實(shí)際應(yīng)用中通過協(xié)議來規(guī)定代理雙方的行為汽抚,協(xié)議中的內(nèi)容一般都是方法列表抓狭,當(dāng)然也可以定義屬性。
協(xié)議是公共的定義造烁,如果只是某個(gè)類使用否过,我們常做的就是寫在某個(gè)類中午笛。如果是多個(gè)類都是用同一個(gè)協(xié)議,建議創(chuàng)建一個(gè)Protocol
文件苗桂,在這個(gè)文件中定義協(xié)議药磺。遵循的協(xié)議可以被繼承,例如我們常用的UITableView
誉察,由于繼承自UIScrollView
的緣故与涡,所以也將UIScrollViewDelegate
繼承了過來,我們可以通過代理方法獲取UITableView
偏移量等狀態(tài)參數(shù)持偏。
協(xié)議只能定義公用的一套接口驼卖,類似于一個(gè)約束代理雙方的作用。但不能提供具體的實(shí)現(xiàn)方法鸿秆,實(shí)現(xiàn)方法需要代理對(duì)象去實(shí)現(xiàn)酌畜。協(xié)議可以繼承其他協(xié)議,并且可以繼承多個(gè)協(xié)議卿叽,在iOS
中對(duì)象是不支持多繼承的桥胞,而協(xié)議可以多繼承。
// 當(dāng)前協(xié)議繼承了三個(gè)協(xié)議考婴,這樣其他三個(gè)協(xié)議中的方法列表都會(huì)被繼承過來
@protocol LoginProtocol <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>
- (void)userLoginWithUsername:(NSString *)username password:(NSString *)password;
@end
協(xié)議有兩個(gè)修飾符@optional
和@required
贩虾,創(chuàng)建一個(gè)協(xié)議如果沒有聲明,默認(rèn)是@required
狀態(tài)的沥阱。這兩個(gè)修飾符只是約定代理是否強(qiáng)制需要遵守協(xié)議缎罢,如果@required
狀態(tài)的方法代理沒有遵守,會(huì)報(bào)一個(gè)黃色的警告考杉,只是起一個(gè)約束的作用策精,沒有其他功能。
無論是@optional
還是@required
崇棠,在委托方調(diào)用代理方法時(shí)都需要做一個(gè)判斷咽袜,判斷代理是否實(shí)現(xiàn)當(dāng)前方法,否則會(huì)導(dǎo)致崩潰枕稀。
示例:
// 判斷代理對(duì)象是否實(shí)現(xiàn)這個(gè)方法询刹,沒有實(shí)現(xiàn)會(huì)導(dǎo)致崩潰
if ([self.delegate respondsToSelector:@selector(userLoginWithUsername:password:)]) {
[self.delegate userLoginWithUsername:self.username.text password:self.password.text];
}
舉例
假設(shè)我在公司正在敲代碼,敲的正開心呢萎坷,突然口渴了范抓,想喝一瓶紅茶。這時(shí)我就可以拿起手機(jī)去外賣app上定一個(gè)紅茶食铐,然后外賣app就會(huì)下單給店鋪并讓店鋪給我送過來匕垫。
這個(gè)過程中,外賣app就是我的代理虐呻,我就是委托方象泵,我買了一瓶紅茶并付給外賣app錢寞秃,這就是購買協(xié)議。我只需要從外賣app上購買就可以偶惠,具體的操作都由外賣app去處理春寿,我只需要最后接收這瓶紅茶就可以。我付的錢就是參數(shù)忽孽,最后送過來的紅茶就是處理結(jié)果绑改。
但是我買紅茶的同時(shí),我還想吃一份必勝客披薩兄一,我需要另外向必勝客app去訂餐厘线,上面的外賣app并沒有這個(gè)功能。我又向必勝客購買了一份披薩出革,必勝客當(dāng)做我的代理去為我做這份披薩造壮,并最后送到我手里。這就是多個(gè)代理對(duì)象骂束,我就是委托方耳璧。
在iOS
中一個(gè)代理可以有多個(gè)委托方,而一個(gè)委托方也可以有多個(gè)代理展箱。我指定了外賣app和必勝客兩個(gè)代理旨枯,也可以再指定麥當(dāng)勞等多個(gè)代理,委托方也可以為多個(gè)代理服務(wù)混驰。
代理對(duì)象在很多情況下其實(shí)是可以復(fù)用的攀隔,可以創(chuàng)建多個(gè)代理對(duì)象為多個(gè)委托方服務(wù),在下面將會(huì)通過一個(gè)小例子介紹一下控制器代理的復(fù)用账胧。
實(shí)現(xiàn)
首先定義一個(gè)協(xié)議類,來定義公共協(xié)議
#import <Foundation/Foundation.h>
@protocol LoginProtocol <NSObject>
@optional
- (void)userLoginWithUsername:(NSString *)username password:(NSString *)password;
@end
定義委托類先紫,這里簡單實(shí)現(xiàn)了一個(gè)用戶登錄功能治泥,將用戶登錄后的賬號(hào)密碼傳遞出去,有代理來處理具體登錄細(xì)節(jié)遮精。
#import <UIKit/UIKit.h>
#import "LoginProtocol.h"
/**
* 當(dāng)前類是委托類居夹。用戶登錄后,讓代理對(duì)象去實(shí)現(xiàn)登錄的具體細(xì)節(jié)本冲,委托類不需要知道其中實(shí)現(xiàn)的具體細(xì)節(jié)准脂。
*/
@interface LoginViewController : UIViewController
// 通過屬性來設(shè)置代理對(duì)象
@property (nonatomic, weak) id<LoginProtocol> delegate;
@end
實(shí)現(xiàn)部分:
@implementation LoginViewController
- (void)loginButtonClick:(UIButton *)button {
// 判斷代理對(duì)象是否實(shí)現(xiàn)這個(gè)方法,沒有實(shí)現(xiàn)會(huì)導(dǎo)致崩潰
if ([self.delegate respondsToSelector:@selector(userLoginWithUsername:password:)]) {
// 調(diào)用代理對(duì)象的登錄方法檬洞,代理對(duì)象去實(shí)現(xiàn)登錄方法
[self.delegate userLoginWithUsername:self.username.text password:self.password.text];
}
}
代理方狸膏,實(shí)現(xiàn)具體的登錄流程,委托方不需要知道實(shí)現(xiàn)細(xì)節(jié)添怔。
// 遵守登錄協(xié)議
@interface ViewController () <LoginProtocol>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LoginViewController *loginVC = [[LoginViewController alloc] init];
loginVC.delegate = self;
[self.navigationController pushViewController:loginVC animated:YES];
}
/**
* 代理方實(shí)現(xiàn)具體登錄細(xì)節(jié)
*/
- (void)userLoginWithUsername:(NSString *)username password:(NSString *)password {
NSLog(@"username : %@, password : %@", username, password);
}
代理使用原理
代理實(shí)現(xiàn)流程
在iOS
中代理的本質(zhì)就是代理對(duì)象內(nèi)存的傳遞和操作
我們?cè)谖蓄愒O(shè)置代理對(duì)象后湾戳,實(shí)際上只是用一個(gè)id
類型的指針將代理對(duì)象進(jìn)行了一個(gè)弱引用贤旷。委托方讓代理方執(zhí)行操作,實(shí)際上是在委托類中向這個(gè)id
類型指針指向的對(duì)象發(fā)送消息砾脑,而這個(gè)id
類型指針指向的對(duì)象幼驶,就是代理對(duì)象。
通過上面這張圖我們發(fā)現(xiàn)韧衣,其實(shí)委托方的代理屬性本質(zhì)上就是代理對(duì)象自身盅藻,設(shè)置委托代理就是代理屬性指針指向代理對(duì)象,相當(dāng)于代理對(duì)象只是在委托方中調(diào)用自己的方法畅铭,如果方法沒有實(shí)現(xiàn)就會(huì)導(dǎo)致崩潰氏淑。從崩潰的信息上來看,就可以看出來是代理方?jīng)]有實(shí)現(xiàn)協(xié)議中的方法導(dǎo)致的崩潰顶瞒。
而協(xié)議只是一種語法夸政,是聲明委托方中的代理屬性可以調(diào)用協(xié)議中聲明的方法,而協(xié)議中方法的實(shí)現(xiàn)還是有代理方完成榴徐,而協(xié)議方和委托方都不知道代理方有沒有完成守问,也不需要知道怎么完成。
代理內(nèi)存管理
為什么我們?cè)O(shè)置代理屬性都使用weak呢坑资?
我們定義的指針默認(rèn)都是__strong
類型的耗帕,而屬性本質(zhì)上也是一個(gè)成員變量和set、get
方法構(gòu)成的袱贮,strong
類型的指針會(huì)造成強(qiáng)引用仿便,必定會(huì)影響一個(gè)對(duì)象的生命周期,這也就會(huì)形成循環(huán)引用攒巍。
強(qiáng)引用
上圖中嗽仪,由于代理對(duì)象使用強(qiáng)引用指針,引用創(chuàng)建的委托方LoginVC
對(duì)象柒莉,并且成為LoginVC
的代理闻坚。這就會(huì)導(dǎo)致LoginVC
的delegate
屬性強(qiáng)引用代理對(duì)象,導(dǎo)致循環(huán)引用的問題兢孝,最終兩個(gè)對(duì)象都無法正常釋放窿凤。
弱引用
我們將LoginVC對(duì)象的delegate屬性,設(shè)置為弱引用屬性跨蟹。這樣在代理對(duì)象生命周期存在時(shí)雳殊,可以正常為我們工作,如果代理對(duì)象被釋放窗轩,委托方和代理對(duì)象都不會(huì)因?yàn)閮?nèi)存釋放導(dǎo)致的Crash夯秃。
weak還是assign
下面兩種方式都是弱引用代理對(duì)象,但是第一種在代理對(duì)象被釋放后不會(huì)導(dǎo)致崩潰,而第二種會(huì)導(dǎo)致崩潰寝并。
@property (nonatomic, weak) id<LoginProtocol> delegate;
@property (nonatomic, assign) id<LoginProtocol> delegate;
weak
和assign
是一種“非擁有關(guān)系”的指針箫措,通過這兩種修飾符修飾的指針變量,都不會(huì)改變被引用對(duì)象的引用計(jì)數(shù)衬潦。但是在一個(gè)對(duì)象被釋放后斤蔓,weak會(huì)自動(dòng)將指針指向nil
,而assign
則不會(huì)镀岛。在iOS
中弦牡,向nil
發(fā)送消息時(shí)不會(huì)導(dǎo)致崩潰的,所以assign
就會(huì)導(dǎo)致野指針的錯(cuò)誤unrecognized selector sent to instance
漂羊。
所以我們?nèi)绻揎棿韺傩约菝蹋€是用weak修飾吧,比較安全走越。
控制器瘦身-代理對(duì)象
為什么要使用代理對(duì)象椭豫?
隨著項(xiàng)目越來越復(fù)雜,控制器也隨著業(yè)務(wù)的增加而變得越來越臃腫旨指。對(duì)于這種情況赏酥,很多人都想到了最近比較火的MVVM設(shè)計(jì)模式。但是這種模式學(xué)習(xí)曲線很大不好掌握谆构,對(duì)于新項(xiàng)目來說可以使用裸扶,對(duì)于一個(gè)已經(jīng)很復(fù)雜的大中型項(xiàng)目,就不太好動(dòng)框架這層的東西了搬素。
在項(xiàng)目中用到比較多的控件應(yīng)該就有UITableView
了呵晨,有的頁面往往UITableView
的處理邏輯很多,這就是導(dǎo)致控制器臃腫的一個(gè)很大的原因熬尺。對(duì)于這種問題摸屠,我們可以考慮給控制器瘦身,通過代理對(duì)象的方式給控制器瘦身粱哼。
什么是代理對(duì)象
這是平臣径控制器使用UITableView
(圖畫的難看,主要是意思理解就行)
這是我們優(yōu)化之后的控制器構(gòu)成
從上面兩張圖可以看出皂吮,我們將UITableView
的delegate
和DataSource
單獨(dú)拿出來戒傻,由一個(gè)代理對(duì)象類進(jìn)行控制税手,只將必須控制器處理的邏輯傳遞給控制器處理蜂筹。
UITableView
的數(shù)據(jù)處理、展示邏輯和簡單的邏輯交互都由代理對(duì)象去處理芦倒,和控制器相關(guān)的邏輯處理傳遞出來艺挪,交由控制器來處理,這樣控制器的工作少了很多,而且耦合度也大大降低了麻裳。這樣一來口蝠,我們只需要將需要處理的工作交由代理對(duì)象處理,并傳入一些參數(shù)即可津坑。