《52個(gè)有效方法》筆記5——協(xié)議與分類的一些細(xì)節(jié)


代理模式的兩種細(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ě)在主類中。**

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容