標籤:圖例 定義 使用者登入 使用 類型 ble post AC .com
轉自 <簡書 — 劉小壯>
代理的基本使用
代理是一種通用的設計模式,在iOS
中對代理設計模式支援的很好,有特定的文法來實現代理模式,OC語言可以通過@Protocol
實現協議。
代理主要由三部分組成:
- 協議:用來指定代理雙方可以做什麼,必須做什麼。
- 代理:根據指定的協議,完成委託方需要實現的功能。
- 委託:根據指定的協議,指定代理去完成什麼功能。
這裡用一張圖來闡述一下三方之間的關係:
Protocol-協議的概念
從中我們可以看到三方之間的關係,在實際應用中通過協議來規定代理雙方的行為,協議中的內容一般都是方法列表,當然也可以定義屬性。
協議是公用的定義,如果只是某個類使用,我們常做的就是寫在某個類中。如果是多個類都是用同一個協議,建議建立一個Protocol
檔案,在這個檔案中定義協議。遵循的協議可以被繼承,例如我們常用的UITableView
,由於繼承自UIScrollView
的緣故,所以也將UIScrollViewDelegate
繼承了過來,我們可以通過代理方法擷取UITableView
位移量等狀態參數。
協議只能定義公用的一套介面,類似於一個約束代理雙方的作用。但不能提供具體的實現方法,實現方法需要代理對象去實現。協議可以繼承其他協議,並且可以繼承多個協議,在iOS
中對象是不支援多繼承的,而協議可以多繼承。
// 當前協議繼承了三個協議,這樣其他三個協議中的方法列表都會被繼承過來@protocol LoginProtocol <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>- (void)userLoginWithUsername:(NSString *)username password:(NSString *)password;@end
協議有兩個修飾符@optional
和@required
,建立一個協議如果沒有聲明,預設是@required
狀態的。這兩個修飾符只是約定代理是否強制需要遵守協議,如果@required
狀態的方法代理沒有遵守,會報一個黃色的警告,只是起一個約束的作用,沒有其他功能。
無論是@optional
還是@required
,在委託方調用代理方法時都需要做一個判斷,判斷代理是否實現當前方法,否則會導致崩潰。
樣本:
// 判斷代理對象是否實現這個方法,沒有實現會導致崩潰if ([self.delegate respondsToSelector:@selector(userLoginWithUsername:password:)]) { [self.delegate userLoginWithUsername:self.username.text password:self.password.text];}
舉例
假設我在公司正在敲代碼,敲的正開心呢,突然口渴了,想喝一瓶紅茶。這時我就可以拿起手機去外賣app上定一個紅茶,然後外賣app就會下單給店鋪並讓店鋪給我送過來。
這個過程中,外賣app就是我的代理,我就是委託方,我買了一瓶紅茶並付給外賣app錢,這就是購買協議。我只需要從外賣app上購買就可以,具體的操作都由外賣app去處理,我只需要最後接收這瓶紅茶就可以。我付的錢就是參數,最後送過來的紅茶就是處理結果。
但是我買紅茶的同時,我還想吃一份必勝客披薩,我需要另外向必勝客app去訂餐,上面的外賣app並沒有這個功能。我又向必勝客購買了一份披薩,必勝客當做我的代理去為我做這份披薩,並最後送到我手裡。這就是多個代理對象,我就是委託方。
在iOS
中一個代理可以有多個委託方,而一個委託方也可以有多個代理。我指定了外賣app和必勝客兩個代理,也可以再指定麥當勞等多個代理,委託方也可以為多個代理服務。
代理對象在很多情況下其實是可以複用的,可以建立多個代理對象為多個委託方服務,在下面將會通過一個小例子介紹一下控制器代理的複用。
實現
首先定義一個協議類,來定義公用協議
#import <Foundation/Foundation.h>@protocol LoginProtocol <NSObject>@optional- (void)userLoginWithUsername:(NSString *)username password:(NSString *)password;@end
定義委託類,這裡簡單實現了一個使用者登入功能,將使用者登入後的帳號密碼傳遞出去,有代理來處理具體登入細節。
#import <UIKit/UIKit.h>#import "LoginProtocol.h"/** * 當前類是委託類。使用者登入後,讓代理對象去實現登入的具體細節,委託類不需要知道其中實現的具體細節。 */@interface LoginViewController : UIViewController// 通過屬性來設定代理對象@property (nonatomic, weak) id<LoginProtocol> delegate;@end實現部分:@implementation LoginViewController- (void)loginButtonClick:(UIButton *)button { // 判斷代理對象是否實現這個方法,沒有實現會導致崩潰 if ([self.delegate respondsToSelector:@selector(userLoginWithUsername:password:)]) { // 調用代理對象的登入方法,代理對象去實現登入方法 [self.delegate userLoginWithUsername:self.username.text password:self.password.text]; }}
代理方,實現具體的登入流程,委託方不需要知道實現細節。
// 遵守登入協議@interface ViewController () <LoginProtocol> @end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; LoginViewController *loginVC = [[LoginViewController alloc] init]; loginVC.delegate = self; [self.navigationController pushViewController:loginVC animated:YES];}/** * 代理方實現具體登入細節 */- (void)userLoginWithUsername:(NSString *)username password:(NSString *)password { NSLog(@"username : %@, password : %@", username, password);}
代理使用原理代理實現流程
在iOS
中代理的本質就是代理對象記憶體的傳遞和操作
我們在委託類設定代理對象後,實際上只是用一個id
類型的指標將代理對象進行了一個弱引用。委託方讓代理方執行操作,實際上是在委託類中向這個id
類型指標指向的對象發送訊息,而這個id
類型指標指向的對象,就是代理對象。
通過上面這張圖我們發現,其實委託方的代理屬性本質上就是代理對象自身,設定委託代理就是代理屬性指標指向代理對象,相當於代理對象只是在委託方中調用自己的方法,如果方法沒有實現就會導致崩潰。從崩潰的資訊上來看,就可以看出來是代理方沒有實現協議中的方法導致的崩潰。
而協議只是一種文法,是聲明委託方中的代理屬性可以調用協議中聲明的方法,而協議中方法的實現還是有代理方完成,而協議方和委託方都不知道代理方有沒有完成,也不需要知道怎麼完成。
代理記憶體管理
為什麼我們設定代理屬性都使用weak呢?
我們定義的指標預設都是__strong
類型的,而屬性本質上也是一個成員變數和set、get
方法構成的,strong
類型的指標會造成強引用,必定會影響一個對象的生命週期,這也就會形成循環參考。
強引用
中,由於代理對象使用強引用指標,引用建立的委託方LoginVC
對象,並且成為LoginVC
的代理。這就會導致LoginVC
的delegate
屬性強引用代理對象,導致循環參考的問題,最終兩個對象都無法正常釋放。
弱引用
我們將LoginVC對象的delegate屬性,設定為弱引用屬性。這樣在代理對象生命週期存在時,可以正常為我們工作,如果代理對象被釋放,委託方和代理對象都不會因為記憶體釋放導致的Crash。
weak還是assign
下面兩種方式都是弱引用代理對象,但是第一種在代理對象被釋放後不會導致崩潰,而第二種會導致崩潰。
@property (nonatomic, weak) id<LoginProtocol> delegate;@property (nonatomic, assign) id<LoginProtocol> delegate;
weak
和assign
是一種“非擁有關係”的指標,通過這兩種修飾符修飾的指標變數,都不會改變被引用對象的引用計數。但是在一個對象被釋放後,weak會自動將指標指向nil
,而assign
則不會。在iOS
中,向nil
發送訊息時不會導致崩潰的,所以assign
就會導致野指標的錯誤unrecognized selector sent to instance
。
所以我們如果修飾代理屬性,還是用weak修飾吧,比較安全。
控制器瘦身-代理對象為什麼要使用代理對象?
隨著項目越來越複雜,控制器也隨著業務的增加而變得越來越臃腫。對於這種情況,很多人都想到了最近比較火的MVVM設計模式。但是這種模式學習曲線很大不好掌握,對於新項目來說可以使用,對於一個已經很複雜的大中型項目,就不太好動架構這層的東西了。
在項目中用到比較多的控制項應該就有UITableView
了,有的頁面往往UITableView
的處理邏輯很多,這就是導致控制器臃腫的一個很大的原因。對於這種問題,我們可以考慮給控制器瘦身,通過代理對象的方式給控制器瘦身。
什麼是代理對象
這是平常控制器使用UITableView
(圖畫的難看,主要是意思理解就行)
這是我們最佳化之後的控制器構成
從上面兩張圖可以看出,我們將UITableView
的delegate
和DataSource
單獨拿出來,由一個代理對象類進行控制,只將必須控制器處理的邏輯傳遞給控制器處理。
UITableView
的資料處理、展示邏輯和簡單的邏輯互動都由代理對象去處理,和控制器相關的邏輯處理傳遞出來,交由控制器來處理,這樣控制器的工作少了很多,而且耦合度也大大降低了。這樣一來,我們只需要將需要處理的工作交由代理對象處理,並傳入一些參數即可。
iOS 代理設計模式