代理模式的兩種細(xì)分
代理模式也叫委托模式叙赚,我們在這統(tǒng)一一下稱呼老客。A類要委托B類來做某事,則A叫“委托者”震叮,因?yàn)锳委托胧砰、托付B幫它做事;把B叫“代理者”冤荆,因?yàn)锽幫助朴则、代理A完成該事。
在代理模式里钓简,代理者需要實(shí)現(xiàn)定義有關(guān)該“委托之事”規(guī)范的協(xié)議乌妒,說起協(xié)議⊥獾耍可以細(xì)分為“普通代理協(xié)議”和“數(shù)據(jù)源協(xié)議”撤蚊。這個在UITableVIew中體現(xiàn)得很明顯。
** UITableViewDelegate
協(xié)議就是普通的代理協(xié)議损话。**
總得來說侦啸,在下面這個方法中,** 數(shù)據(jù)是從“委托者”(UITableView)通過參數(shù)流向“代理者”(一般為ViewController)的丧枪。**
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
** 而UITableViewDataSource
協(xié)議就是所謂的“數(shù)據(jù)源協(xié)議”光涂。**
顧名思義,它為“委托者”(UITableView)提供數(shù)據(jù)拧烦⊥牛總得來說,** 它的數(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
我們在自定義的YWAlertView中定義了它的代理者的屬性delegate來保存债沮、持有代理者炼吴。看上面的代碼疫衩,定義委托者的代理者屬性要注意兩點(diǎn):
- 內(nèi)存管理語義要使用weak硅蹦。因?yàn)榇藭r委托者YWAlertView持有了代理者delegate,而在代理者類ViewController中,一般也要持有YWAlertView對象提针,這樣的話它們兩者互相持有命爬,互相保留,則會形成保留壞辐脖,互不相讓饲宛,都不釋放。這樣就造成了內(nèi)存泄露嗜价。
- 委托者的代理屬性的類型是
id<YWAlertViewDelegate>
這在OC中叫 ** 匿名對象 ** 艇抠,沒有指定它的具體得是什么類,一定得是什么類久锥,它的語義是家淤,遵從YWAlertViewDelegate協(xié)議的任何對象。
** 注意:** OC中的匿名對象和概念和其他編程語言中的有所不同瑟由,其他編程的匿名對象一般指以內(nèi)聯(lián)方式所創(chuàng)建出來的無名類絮重。要注意區(qū)分,以免混淆歹苦。
細(xì)節(jié)優(yōu)化
若協(xié)議中的代理是可選實(shí)現(xiàn)的青伤,則我們在委托類中調(diào)用代理方法時,則需要判斷代理者是否已經(jīng)實(shí)現(xiàn)該協(xié)議里方法殴瘦,判斷代理者能否響應(yīng)此選擇子狠角。
我們知道有些方法在執(zhí)行過程中多次調(diào)用的,比如NSURLConnection網(wǎng)絡(luò)下載返回?cái)?shù)據(jù)的代理方法是多次調(diào)用蚪腋,網(wǎng)絡(luò)數(shù)據(jù)是一段一段下載而來的丰歌。又比如下面UITextFieldDelegate里的方法,也是多次調(diào)用的屉凯。
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;
像下面我們自定義的YWTextfield立帖,它是基于UITextfield而建的,在YWTextField中UITextfieldDelegate的值發(fā)生改變時就調(diào)用我們協(xié)議定義的方法悠砚,所以該協(xié)議方法會被調(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)用前通過respondsToSelector
來檢測代理者能否響應(yīng),是完全沒必要的哩簿,因?yàn)槿舸碚弑旧頉]變,它不太可能突然不能響應(yīng)原本可以響應(yīng)的選擇子酝静。所以节榜,這兒有可優(yōu)化的可能性。比較好的方案是 ** 把代理者能否響應(yīng)該協(xié)議方法這一信息緩存起來别智,以后每次只通過該緩存判斷該協(xié)議方法是否被實(shí)現(xiàn)宗苍。**
像下面的例子,我們重寫代理者屬性ywDelegate的setter方法,在setter方法中通過``respondsToSelector```判斷一次代理者是否實(shí)現(xiàn)該協(xié)議方法讳窟,然后把結(jié)果緩存在“段位”變量_ywdelegateFlags中让歼。這樣當(dāng)我們設(shè)置某類為委托類的代理時,就已經(jīng)開始判斷它是否實(shí)現(xiàn)協(xié)議方法丽啡,且把結(jié)果緩存下來了谋右。
// YWTextfield.m
#import "YWTextfield.h"
// C中的“段位”
struct
{
unsigned int ywtextfieldStartEdit :1;
unsigned int valueIsChanging :1;
// 表示占用一個字節(jié),因此可以代表1 or 0 兩個值
}_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
// 重寫ywDelegate屬性的setter方法补箍,在內(nèi)把代理者是否實(shí)現(xiàn)該協(xié)議方法這一信息緩存下來
- (void)setYwDelegate:(id<YWTextfieldDelegate>)ywDelegate
{
_ywDelegate = ywDelegate;
_ywdelegateFlags.valueIsChanging = [ywDelegate respondsToSelector:@selector(ywtextfield:textValueIsChanging:)];
}
@end
** 注意:** 此處用來緩存代理者能否響應(yīng)該選擇子這一信息的數(shù)據(jù)結(jié)構(gòu)叫“段位”改执。
關(guān)于分類的一些細(xì)節(jié)
OC有分類,當(dāng)我們想給某類添加新方法時坑雅,我們可以為該類定義分類辈挂,將新方法定義在里面,而不需要定義一個繼承于該類的子類裹粤。
** 這么說來终蒂,OC中為某類添加新方法有兩種方案,其一為繼承該類定義子類遥诉,在子類添加拇泣;其二則為添加分類,在分類中定義新方法突那。**
當(dāng)一個類很龐大時挫酿,我們可以根據(jù)功能模塊的不同,而將方法分散到幾個分類中愕难,這樣便于管理早龟。比如,我們把可以像下面這樣拆分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
** 注意:我們把“好友”這一模塊拆分出來建立了FriendShip分類猫缭,指的是把有關(guān)好友的“方法們”拆分出來了葱弟,你可別把friends這個屬性也拆分進(jìn)FriendShip分類中。若把屬性聲明放在了分類中猜丹,編譯時會給出警告芝加。
要記住: 把所有的成員變量好屬性聲明都應(yīng)寫在主類中射窒。**