iOS 代理設(shè)計(jì)模式

轉(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)引用

強(qiáng)引用

上圖中嗽仪,由于代理對(duì)象使用強(qiáng)引用指針,引用創(chuàng)建的委托方LoginVC對(duì)象柒莉,并且成為LoginVC的代理闻坚。這就會(huì)導(dǎo)致LoginVCdelegate屬性強(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;

weakassign是一種“非擁有關(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)成

代理對(duì)象

從上面兩張圖可以看出皂吮,我們將UITableViewdelegateDataSource單獨(dú)拿出來戒傻,由一個(gè)代理對(duì)象類進(jìn)行控制税手,只將必須控制器處理的邏輯傳遞給控制器處理蜂筹。

UITableView的數(shù)據(jù)處理、展示邏輯和簡單的邏輯交互都由代理對(duì)象去處理芦倒,和控制器相關(guān)的邏輯處理傳遞出來艺挪,交由控制器來處理,這樣控制器的工作少了很多,而且耦合度也大大降低了麻裳。這樣一來口蝠,我們只需要將需要處理的工作交由代理對(duì)象處理,并傳入一些參數(shù)即可津坑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末妙蔗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子疆瑰,更是在濱河造成了極大的恐慌眉反,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件穆役,死亡現(xiàn)場離奇詭異寸五,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)耿币,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門梳杏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人淹接,你說我怎么就攤上這事十性。” “怎么了蹈集?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵烁试,是天一觀的道長。 經(jīng)常有香客問我拢肆,道長减响,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任郭怪,我火速辦了婚禮支示,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鄙才。我一直安慰自己颂鸿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布攒庵。 她就那樣靜靜地躺著嘴纺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浓冒。 梳的紋絲不亂的頭發(fā)上栽渴,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音稳懒,去河邊找鬼闲擦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的墅冷。 我是一名探鬼主播纯路,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寞忿!你這毒婦竟也來了驰唬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤腔彰,失蹤者是張志新(化名)和其女友劉穎定嗓,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萍桌,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宵溅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了上炎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恃逻。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖藕施,靈堂內(nèi)的尸體忽然破棺而出寇损,到底是詐尸還是另有隱情,我是刑警寧澤裳食,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布矛市,位于F島的核電站,受9級(jí)特大地震影響诲祸,放射性物質(zhì)發(fā)生泄漏浊吏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一救氯、第九天 我趴在偏房一處隱蔽的房頂上張望找田。 院中可真熱鬧,春花似錦着憨、人聲如沸墩衙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽漆改。三九已至,卻和暖如春准谚,著一層夾襖步出監(jiān)牢的瞬間挫剑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工氛魁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留暮顺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓秀存,卻偏偏與公主長得像捶码,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子或链,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容