代理模式的兩種細(xì)分
代理模式也叫委托模式,我們?cè)谶@統(tǒng)一一下稱呼。A類要委托B類來(lái)做某事,則A叫“委托者”,因?yàn)锳委托、托付B幫它做事;把B叫“代理者”,因?yàn)锽幫助、代理A完成該事。
在代理模式里,代理者需要實(shí)現(xiàn)定義有關(guān)該“委托之事”規(guī)范的協(xié)議,說(shuō)起協(xié)議??梢约?xì)分為“普通代理協(xié)議”和“數(shù)據(jù)源協(xié)議”。這個(gè)在UITableVIew中體現(xiàn)得很明顯。
** UITableViewDelegate協(xié)議就是普通的代理協(xié)議。**
總得來(lái)說(shuō),在下面這個(gè)方法中,** 數(shù)據(jù)是從“委托者”(UITableView)通過(guò)參數(shù)流向“代理者”(一般為ViewController)的。**
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
** 而UITableViewDataSource協(xié)議就是所謂的“數(shù)據(jù)源協(xié)議”。**
顧名思義,它為“委托者”(UITableView)提供數(shù)據(jù)??偟脕?lái)說(shuō),** 它的數(shù)據(jù)是從“代理者”(當(dāng)前ViewController)以返回值的形式流向“委托者”(UITableView)的。**
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
代理模式里“普通代理協(xié)議”和“數(shù)據(jù)源協(xié)議”有以上數(shù)據(jù)流向不同的區(qū)別,望細(xì)辨之。
委托者的代理屬性
// YWAlertView.h
#import <UIKit/UIKit.h>
@class YWAlertView;
@protocol YWAlertViewDelegate <NSObject>
- (void)ywalertView:(YWAlertView *)alertView clickBtnIndex:(NSInteger)index;
@end
@interface YWAlertView : UIAlertView
@property (nonatomic, weak)id<YWAlertViewDelegate> delegate;
@end
我們?cè)谧远x的YWAlertView中定義了它的代理者的屬性delegate來(lái)保存、持有代理者??瓷厦娴拇a,定義委托者的代理者屬性要注意兩點(diǎn):
- 內(nèi)存管理語(yǔ)義要使用weak。因?yàn)榇藭r(shí)委托者YWAlertView持有了代理者delegate,而在代理者類ViewController中,一般也要持有YWAlertView對(duì)象,這樣的話它們兩者互相持有,互相保留,則會(huì)形成保留壞,互不相讓,都不釋放。這樣就造成了內(nèi)存泄露。
- 委托者的代理屬性的類型是
id<YWAlertViewDelegate>這在OC中叫 ** 匿名對(duì)象 ** ,沒(méi)有指定它的具體得是什么類,一定得是什么類,它的語(yǔ)義是,遵從YWAlertViewDelegate協(xié)議的任何對(duì)象。
** 注意:** OC中的匿名對(duì)象和概念和其他編程語(yǔ)言中的有所不同,其他編程的匿名對(duì)象一般指以內(nèi)聯(lián)方式所創(chuàng)建出來(lái)的無(wú)名類。要注意區(qū)分,以免混淆。
細(xì)節(jié)優(yōu)化
若協(xié)議中的代理是可選實(shí)現(xiàn)的,則我們?cè)谖蓄愔姓{(diào)用代理方法時(shí),則需要判斷代理者是否已經(jīng)實(shí)現(xiàn)該協(xié)議里方法,判斷代理者能否響應(yīng)此選擇子。
我們知道有些方法在執(zhí)行過(guò)程中多次調(diào)用的,比如NSURLConnection網(wǎng)絡(luò)下載返回?cái)?shù)據(jù)的代理方法是多次調(diào)用,網(wǎng)絡(luò)數(shù)據(jù)是一段一段下載而來(lái)的。又比如下面UITextFieldDelegate里的方法,也是多次調(diào)用的。
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;
像下面我們自定義的YWTextfield,它是基于UITextfield而建的,在YWTextField中UITextfieldDelegate的值發(fā)生改變時(shí)就調(diào)用我們協(xié)議定義的方法,所以該協(xié)議方法會(huì)被調(diào)用多次。
// YWTextfield.h
#import <UIKit/UIKit.h>
@class YWTextfield;
@protocol YWTextfieldDelegate <NSObject>
- (void)ywtextfield:(YWTextfield *)textfield textValueIsChanging:(NSString *)text;
@end
@interface YWTextfield : UITextField
@property (nonatomic, weak)id<YWTextfieldDelegate> delegate;
@end
// YWTextfield.m
#import "YWTextfield.h"
@interface YWTextfield ()<UITextFieldDelegate>
@end
@implementation YWTextfield
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if([self.delegate respondsToSelector:@selector(ywtextfield:textValueIsChanging:)])
{
[self.delegate ywtextfield:self textValueIsChanging:string];
return YES;
}
return NO;
}
@end
其實(shí)每次調(diào)用前通過(guò)respondsToSelector來(lái)檢測(cè)代理者能否響應(yīng),是完全沒(méi)必要的,因?yàn)槿舸碚弑旧頉](méi)變,它不太可能突然不能響應(yīng)原本可以響應(yīng)的選擇子。所以,這兒有可優(yōu)化的可能性。比較好的方案是 ** 把代理者能否響應(yīng)該協(xié)議方法這一信息緩存起來(lái),以后每次只通過(guò)該緩存判斷該協(xié)議方法是否被實(shí)現(xiàn)。**
像下面的例子,我們重寫(xiě)代理者屬性ywDelegate的setter方法,在setter方法中通過(guò)``respondsToSelector```判斷一次代理者是否實(shí)現(xiàn)該協(xié)議方法,然后把結(jié)果緩存在“段位”變量_ywdelegateFlags中。這樣當(dāng)我們?cè)O(shè)置某類為委托類的代理時(shí),就已經(jīng)開(kāi)始判斷它是否實(shí)現(xiàn)協(xié)議方法,且把結(jié)果緩存下來(lái)了。
// YWTextfield.m
#import "YWTextfield.h"
// C中的“段位”
struct
{
unsigned int ywtextfieldStartEdit :1;
unsigned int valueIsChanging :1;
// 表示占用一個(gè)字節(jié),因此可以代表1 or 0 兩個(gè)值
}_ywdelegateFlags;
@interface YWTextfield ()<YWTextfieldDelegate>
@end
@implementation YWTextfield
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(_ywdelegateFlags.valueIsChanging)
{
[self.ywDelegate ywtextfield:self textValueIsChanging:string];
return YES;
}
return NO;
}
#pragma mark ---- setter
// 重寫(xiě)ywDelegate屬性的setter方法,在內(nèi)把代理者是否實(shí)現(xiàn)該協(xié)議方法這一信息緩存下來(lái)
- (void)setYwDelegate:(id<YWTextfieldDelegate>)ywDelegate
{
_ywDelegate = ywDelegate;
_ywdelegateFlags.valueIsChanging = [ywDelegate respondsToSelector:@selector(ywtextfield:textValueIsChanging:)];
}
@end
** 注意:** 此處用來(lái)緩存代理者能否響應(yīng)該選擇子這一信息的數(shù)據(jù)結(jié)構(gòu)叫“段位”。
關(guān)于分類的一些細(xì)節(jié)
OC有分類,當(dāng)我們想給某類添加新方法時(shí),我們可以為該類定義分類,將新方法定義在里面,而不需要定義一個(gè)繼承于該類的子類。
** 這么說(shuō)來(lái),OC中為某類添加新方法有兩種方案,其一為繼承該類定義子類,在子類添加;其二則為添加分類,在分類中定義新方法。**
當(dāng)一個(gè)類很龐大時(shí),我們可以根據(jù)功能模塊的不同,而將方法分散到幾個(gè)分類中,這樣便于管理。比如,我們把可以像下面這樣拆分Person類
// Person主類
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy)NSString *personId;
@property (nonatomic, copy)NSString *name;
@property (nonatomic, strong)NSArray *friends;
@end
// "工作"某塊
@interface Person (Work)
- (void)goToCode;
- (void)writeBlog;
@end
// "好友"模塊
@interface Person (FriendShip)
- (void)addFriend:(Person *)person;
- (void)removeFriend:(Person *)person;
@end
** 注意:我們把“好友”這一模塊拆分出來(lái)建立了FriendShip分類,指的是把有關(guān)好友的“方法們”拆分出來(lái)了,你可別把friends這個(gè)屬性也拆分進(jìn)FriendShip分類中。若把屬性聲明放在了分類中,編譯時(shí)會(huì)給出警告。
要記?。?把所有的成員變量好屬性聲明都應(yīng)寫(xiě)在主類中。**