編寫高質(zhì)量iOS與OSX代碼的52個有效方法-第四章:協(xié)議與封裝

協(xié)議(protocol)與java的接口類似刊侯。CO不支持多重繼承汁政,因而吧某個類應(yīng)該實現(xiàn)的一系列方法定義在協(xié)議里勉躺。協(xié)議最常見的用途是事先委托模式,也有其他用法瓮下。

分類(Category)是OC一項重要語言特性翰铡。利用分類機制,無需繼承子類就可以直接為當(dāng)前類添加方法讽坏。由于OC運行期系統(tǒng)是高度動態(tài)的坠陈,所以才能支持這一特性食零,也有一些坑膛薛。

23轻掩、通過委托與數(shù)據(jù)源協(xié)議進行對象間通信

委托模式(Delegate pattern):定義一套接口,某對象若想接受另一個對象的委托胀葱,則需遵從次接口漠秋,以便成為其委托對象(delegate)。而這“另一個對象”可以給其委托對象回傳一些消息抵屿,也可以在發(fā)生相關(guān)事件時通知委托對象庆锦。

此模式可將數(shù)據(jù)與業(yè)務(wù)邏輯解耦。

視圖對象中可以包含負責(zé)數(shù)據(jù)與事件處理的對象晌该,這兩種對象分別稱為數(shù)據(jù)源(data source)與委托(delegate)肥荔。

代理方法:

#import <Foundation/Foundation.h>

@class ZYDNetworkFetcher;

@protocol ZYDNetrokFetcherDelegate <NSObject>
// 一般默認方法是必選的
- (void)networkFetcherRequestFinished:(ZYDNetworkFetcher *)fetcher;

// 委托協(xié)議中的方法一般是可選的(optional)
@optional
- (void)networkFetcher:(ZYDNetworkFetcher *)fetcher didReceivdData:(NSData *)data;

- (void)networdFetcher:(ZYDNetworkFetcher *)fetcher didFailWithError:(NSError *)error;

@end

發(fā)起委托

.h

#import <Foundation/Foundation.h>
#import "ZYDNetrokFetcherDelegate.h"

@interface ZYDNetworkFetcher : NSObject
//用weak而不是strong,兩者之間必須為非擁有關(guān)系朝群。
//想要使用ZYDNetworkFetcher對象的那個而對象會持有本對象燕耿,知道用完本對象之后,才會釋放姜胖。
//如果聲明屬性的時候用strong將本對象與委托對象之間定為擁有關(guān)系誉帅,那么就會引入保留換(retain cycle)
//所以,本類中存放委托對象的這個屬性要么定義weak 要么定義unsafe_unretained右莱。
//在相關(guān)對象銷毀時自動清空蚜锨,使用weak,不需要自動清空慢蜓,使用后者亚再。
@property (nonatomic,weak) id <ZYDNetrokFetcherDelegate> delegate;

@end


.m

#import "ZYDNetworkFetcher.h"

@implementation ZYDNetworkFetcher

- (void)requestFinishedWithData:(NSData *)data {
    if ([_delegate respondsToSelector:@selector(networkFetcher:didReceivdData:)]) {
        [_delegate networkFetcher:self didReceivdData:data];
    }
}

- (void)reqeustFailedWithError:(NSError *)error {
    if ([_delegate respondsToSelector:@selector(networdFetcher:didFailWithError:)]) {
        [_delegate networdFetcher:self didFailWithError:error];
    }
}

@end

委托對象

#import "ZYDDataModel.h"
#import "ZYDNetrokFetcherDelegate.h"

// 實現(xiàn)委托對象的辦法,是聲明遵從委托協(xié)議
// 然后把協(xié)議中想實現(xiàn)的方法在類里實現(xiàn)出來
@interface ZYDDataModel () <ZYDNetrokFetcherDelegate>
@end

@implementation ZYDDataModel

#pragma mark -- 實現(xiàn)協(xié)議方法
- (void)networkFetcherRequestFinished:(ZYDNetworkFetcher *)fetcher {
    
}

- (void)networkFetcher:(ZYDNetworkFetcher *)fetcher didReceivdData:(NSData *)data {
    
}

- (void)networdFetcher:(ZYDNetworkFetcher *)fetcher didFailWithError:(NSError *)error {
    
}
@end

數(shù)據(jù)源模式:信息從數(shù)據(jù)源流向類晨抡。
常規(guī)的委托模式氛悬,信息則從類流向受委托者。

一個類中相關(guān)方法要調(diào)用很多次的時候耘柱,需要反復(fù)調(diào)用判斷邏輯[_delegate respondsToSelector:@selector(networdFetcher:didFailWithError:)]如捅。可以使用結(jié)構(gòu)體调煎,將結(jié)果標記緩存下來镜遣,只判斷標記,不在調(diào)用判斷語句士袄。

#import "ZYDNetworkFetcher.h"

@interface ZYDNetworkFetcher () {
    struct {
        unsigned int didReceiveData: 1;
        unsigned int didFailWIthError : 1;
        
    } _delegateFlags;
}
@end

@implementation ZYDNetworkFetcher


- (void)setDelegate:(id<ZYDNetrokFetcherDelegate>)delegate {
    _delegate = delegate;
    _delegateFlags.didReceiveData = [_delegate respondsToSelector:@selector(networkFetcher:didReceivdData:)];
    _delegateFlags.didFailWIthError = [_delegate respondsToSelector:@selector(networdFetcher:didFailWithError:)];
}

- (void)requestFinishedWithData:(NSData *)data {
    if (_delegateFlags.didReceiveData) {
        [_delegate networkFetcher:self didReceivdData:data];
    }
}

- (void)reqeustFailedWithError:(NSError *)error {
    if (_delegateFlags.didFailWIthError) {
        [_delegate networdFetcher:self didFailWithError:error];
    }
}

@end

  • 委托模式為對象提供了一套接口悲关,使其可由此將相關(guān)事件告知其他對象。
  • 將委托對象應(yīng)該支持的接口定義成協(xié)議娄柳,在協(xié)議中把可能需要處理的時間定義成方法坚洽。
  • 當(dāng)某對象需要從另外一個對象中獲取數(shù)據(jù)時,可以使用委托模式西土,這種情況下亦稱為數(shù)據(jù)源協(xié)議讶舰。
  • 如果有必要,可實現(xiàn)含有位段的結(jié)構(gòu)體需了,將委托對象是否能響應(yīng)相關(guān)協(xié)議方法這一信息緩存至其中跳昼。

24、將類的實現(xiàn)代碼分散到便于管理的數(shù)個分類之中

OC分類(類別/Category)機制肋乍,把類代碼按邏輯劃入幾個分區(qū)中鹅颊,這對開發(fā)與調(diào)試都有好處。

將代碼按照方法分成好幾個部分墓造,類的基本要輸都聲明在主實現(xiàn)文件中堪伍,執(zhí)行不同類型的操作作用的另外幾套方法歸入到各個分類中锚烦。

  • 使用分類機制以后,依然可以把整個類都定義在一個接口文件中帝雇,并將其代碼寫在一個實現(xiàn)文件里涮俄。
.h

#import <Foundation/Foundation.h>

@interface ZYDPersonModel : NSObject

@property (nonatomic,copy,readonly) NSString *firstName;

@property (nonatomic,copy,readonly) NSString *lastName;

@property (nonatomic,strong,readonly) NSArray *friends;

- (instancetype)initWithFristName:(NSString *)firstName lastName:(NSString *)lastName;

@end

@interface ZYDPersonModel (FriendShip)

- (void)addFried:(ZYDPersonModel *)person;

- (void)removeFriend:(ZYDPersonModel *)person;

- (BOOL)isFriendWith:(ZYDPersonModel *)person;

@end

@interface ZYDPersonModel (Wrok)

- (void)performDaysWork;

@end

.m

#import "ZYDPersonModel.h"

@interface ZYDPersonModel ()

@property (nonatomic,readwrite) NSMutableArray *internalFriends;
@end

@implementation ZYDPersonModel

- (instancetype)initWithFristName:(NSString *)firstName lastName:(NSString *)lastName {
    if (self = [super init]) {
        _firstName = [firstName copy];
        _lastName = [lastName copy];
        _internalFriends = [NSMutableArray array];
    }
    return self;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"name :%@ ~ %@ \n frends: %@",_firstName,_lastName,_internalFriends];
}

- (NSArray *)friends {
    return [_internalFriends copy];
}

@end

@implementation ZYDPersonModel(FriendShip)

- (void)addFried:(ZYDPersonModel *)person {
    [_internalFriends addObject:person];
}

- (void)removeFriend:(ZYDPersonModel *)person {
    if ([_internalFriends indexOfObject:person] != NSNotFound) {
        [_internalFriends removeObject:person];
    }
}

- (BOOL)isFriendWith:(ZYDPersonModel *)person {
    if ([_internalFriends indexOfObject:person] != NSNotFound) {
        return YES;
    }
    return NO;
}

@end

@implementation ZYDPersonModel (Wrok)

- (void)performDaysWork {
    NSLog(@"%@ %@ work on Monday",_firstName,_lastName);
}

@end
  • 隨著分類數(shù)量增加,可以各個分類提取到各自的文件中尸闸。
#import "ZYDPersonModel.h"

@interface ZYDPersonModel (Play)

- (void)goToThePark;

@end
#import "ZYDPersonModel+Play.h"

@implementation ZYDPersonModel (Play)

- (void)goToThePark {
    NSLog(@"%@ %@ go to the Park",self.firstName,self.lastName);
}

@end

兩種方式的區(qū)別在于彻亲,分類在一個接口文件中,只需要引入主頭文件即可#import "ZYDPersonModel.h"吮廉。如果單獨提取到各自的文件中苞尝,使用時需要單獨引入頭文件#import "ZYDPersonModel+Play.h"

#import "ViewController.h"

#import "ZYDPersonModel.h"
#import "ZYDPersonModel+Play.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    ZYDPersonModel *personOne = [[ZYDPersonModel alloc] initWithFristName:@"Smith" lastName:@"Json"];
    ZYDPersonModel *personTwo = [[ZYDPersonModel alloc] initWithFristName:@"Wellienm" lastName:@"Jone"];
    [personOne addFried:personTwo];
    NSLog(@"%@",personOne);
    [personOne performDaysWork];
    [personOne goToThePark];
}

@end

通過分類機制,可以把類代碼分成很多個易于管理的小塊宦芦。還有一個好處宙址,將代碼分割成幾塊,把相應(yīng)代碼歸入不同的功能區(qū)(functional area)调卑。

再者曼氛,將類代碼打散到分類,便于調(diào)試令野。

另外舀患,可以創(chuàng)建名為Private的分離,把一些應(yīng)該視為私有方法气破,只會在類或框架內(nèi)部使用聊浅,無需對外公開的方法收入其中。

在編寫準備分享給其他開發(fā)者使用的程序庫時现使,可以考慮創(chuàng)建Private分類低匙。如果程序庫需要用到這些方法,就引入此分類的頭文件碳锈,而分類的頭文件并不隨程序庫一并公開顽冶。


  • 使用分類機制把類的實現(xiàn)代碼劃分成易于管理的小塊。
  • 將應(yīng)該視為私有的方法歸入名叫Private的分類中售碳,以隱藏實現(xiàn)細節(jié)强重。

25、為第三方類的分類名稱加前綴

分類機制通常用于向無源碼的既有類中新增功能贸人。

分類的方法是直接加到類中的间景,就好比是類中固有的方法,將分類方法加入類中這一操作時在運行期系統(tǒng)加載分類時完成的艺智。運行期系統(tǒng)會把分類中所實現(xiàn)的每個方法都加入類的方法列表中倘要。

如果類中本來就有這個方法,分類中又實現(xiàn)了一次十拣,那么分類中方法會覆蓋原來的實現(xiàn)代碼封拧。有可能會發(fā)生多次覆蓋志鹃。

如果多個分類名稱相同,在運行期泽西,是不會報錯的曹铃。但是加載的分類有可能不是你所期望的。

如果有相同的方法尝苇,那么運行時調(diào)用的不一定是你想要的方法。

運行期不會報錯埠胖,但是在實現(xiàn)結(jié)果的時候糠溜,就會出現(xiàn)未知錯誤。

比如實現(xiàn)了NSString的兩個分類直撤,同時都有一個分類方法"- (NSString *)urlEncordedString;"那么在調(diào)用過程中非竿,就不知道是調(diào)用哪個分類中實現(xiàn)的方法。同樣能夠正常編譯通過谋竖。

NSString *urlString = @"http://www.baidu.com";
NSLog(@"%@",[urlString urlEncordedString]);

因為不會報錯红柱,這種問題比較難發(fā)現(xiàn),所以在寫之前就避免這種情況就顯得非常重要蓖乘。

當(dāng)然如果分類名相同锤悄,但是方法名不同時,有可能出現(xiàn)的問題是:No visible @interface for 'NSString' declares the selector 'seconString'嘉抒,你無法調(diào)用自己實現(xiàn)的方法零聚。系統(tǒng)只是提供最后加載到的分類,如此而已些侍。


  • 向第三方庫添加分類時隶症,給分類名稱加上專用前綴。
  • 向第三方庫添加分類時岗宣,給分類方法名加上專用前綴蚂会。

26、 勿在分類中聲明屬性

屬性是封裝數(shù)據(jù)的形式耗式。盡管從技術(shù)上說胁住,分類里也可以聲明屬性,但這種做法還是要盡量避免刊咳。原因在于措嵌,處理實現(xiàn)文件之外,其他分類無法向類中新增實例變量芦缰。因此企巢,他們無法將實現(xiàn)屬性所需的實例變量合成出來。

分類让蕾,應(yīng)將其理解為一種手段浪规,目標在于擴展類的功能或听,而非封裝數(shù)據(jù)。


  • 把封裝數(shù)據(jù)所用的全部屬性都定義在主接口里笋婿。
  • 在實現(xiàn)文件之外的其他分類中誉裆,可以定義存取方法,但盡量不要定義屬性缸濒。

???

27足丢、使用class-continuation分類隱藏實現(xiàn)細節(jié)

class-continuation分類,和普通分類不同庇配,他沒有名字斩跌,它在其所續(xù)接的那個類的實現(xiàn)文件里。重要之處在于捞慌,這是唯一能聲明實例變量的分類耀鸦,而且此分類沒有特定的實現(xiàn)文件,其中的實現(xiàn)方法都應(yīng)該定義在類的實現(xiàn)文件里啸澡。

@interface ZYDPersonModel ()

@end

在class-continuation分類中給類新增實例變量袖订,或者在實現(xiàn)塊增加,可以將其隱藏起來嗅虏,只供本類使用洛姑。

引入C++文件,一般用class-continuation分類解決皮服。

class-continuation還有一種合理用法吏口,將public接口中聲明為只讀的屬性擴展為可讀寫,以便在類的內(nèi)部設(shè)置其值冰更。通常不直接訪問實例變量产徊,而是通過設(shè)置訪問方法來做,應(yīng)為這樣能夠觸發(fā)鍵值觀察蜀细。

出現(xiàn)在class-continuation分類或其他分類中的屬性必須同類接口里的屬性劇痛相同的特質(zhì)(attribute)舟铜,不過只讀狀態(tài)可以擴充為可讀寫。

只會在累的實現(xiàn)代碼中用到的私有方法也可以聲明在class-continuation分類中奠衔。

若對象所遵從的協(xié)議只應(yīng)視為私有谆刨,則可雜class-continuation中聲明。

@interface ZYDDataModel () <ZYDNetrokFetcherDelegate>

@end


  • 通過class-continuation分類向類中新增實例變量归斤。
  • 如果某屬性在主接口中聲明為只讀痊夭,而類的內(nèi)部又要用設(shè)置方法修改此屬性,那么就在class-continuation分類中將其擴展為可讀寫脏里。
  • 把私有方法的原型聲明在class-continuation分類里面她我。
  • 若想使類所遵循的協(xié)議不為人所知,則可于class-continuation分類中聲明。

28番舆、通過協(xié)議提供匿名對象

匿名對象(anonymous object)酝碳,用協(xié)議把自己所寫的API之中的實心細節(jié)隱藏起來,將返回的對象設(shè)計為遵從此協(xié)議的純id類型恨狈。這樣疏哗,想要隱藏的類名就不會出現(xiàn)在API之中。

若是北郵有多個不同的實現(xiàn)類禾怠,不想指明具體使用哪個類返奉,可以考慮用這個方法,這些類有可能會變吗氏,有時候他們又無法容納于標準的類集成體系中芽偏,因而不能以某一個公共基類來統(tǒng)一表示。

例如牲证,在定義受委托者屬性時:@property (nonatomic,weak) id <ZYDNetrokFetcherDelegate> delegate;
任何類的對象都能充當(dāng)代理屬性哮针,即便不繼承自NSObject也可以关面,只要遵循ZYDNetrokFetcherDelegate協(xié)議就行坦袍。對于具備此屬性的類來說,delegate就是匿名的(anonymous)等太。

  • 例如捂齐,實現(xiàn)數(shù)據(jù)庫鏈接
// 通過代理實現(xiàn)方法 
#import <Foundation/Foundation.h>
@protocol ZYDDatabaseConnection <NSObject>

- (void)connect;
- (void)disConnect;
- (BOOL)isConnected;
- (NSSarray *)perforQuery:(NSString *)query;
@end

通過管理器提供數(shù)據(jù)庫鏈接。

#import <Foundation/Foundation.h>
@protocol ZYDDatabaseConnection;
@interface ZYDDatabaseManager : NSObject
+ (id)sharedInstance;
//通過 此返回對象缩抡,實現(xiàn)數(shù)據(jù)庫鏈接奠宜、斷開、查詢方法瞻想,
// 對象類型并不重要压真,重要的是有沒有實現(xiàn)方法。--這種情況下也可以用這些匿名類型(anonymous type)
- (id <ZYDDatabaseConnection>)connectionWithIdentifier:(NSString *)identifier;
@end

可以創(chuàng)建匿名對象將不同的三方類庫包裹一下蘑险,是匿名對象成為其子類滴肿,并遵從ZYDDatabaseConnection協(xié)議。然后用connectionWithIdentifier:返回這些類對象佃迄。后續(xù)版本泼差,無須改變公共API,即可切換后端的實現(xiàn)類呵俏。

(待完善)


  • 協(xié)議可以在某種程度上提供匿名類型堆缘,具體的對象類型可以淡化成遵從某協(xié)議的id類型,協(xié)議里規(guī)定了對象所應(yīng)實現(xiàn)的方法普碎。
  • 使用匿名對象來隱藏類型名稱(或類名)
  • 如果具體類型不重要吼肥,重要的是對象能夠相應(yīng)(定義在協(xié)議里的)特定方法,那么可以使用匿名對象來表示。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末潜沦,一起剝皮案震驚了整個濱河市萄涯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唆鸡,老刑警劉巖涝影,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異争占,居然都是意外死亡燃逻,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門臂痕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伯襟,“玉大人,你說我怎么就攤上這事握童∧饭郑” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵澡绩,是天一觀的道長稽揭。 經(jīng)常有香客問我,道長肥卡,這世上最難降的妖魔是什么溪掀? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮步鉴,結(jié)果婚禮上揪胃,老公的妹妹穿的比我還像新娘。我一直安慰自己氛琢,他們只是感情好喊递,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著阳似,像睡著了一般骚勘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上障般,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天调鲸,我揣著相機與錄音,去河邊找鬼挽荡。 笑死藐石,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的定拟。 我是一名探鬼主播于微,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼逗嫡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了株依?” 一聲冷哼從身側(cè)響起驱证,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恋腕,沒想到半個月后抹锄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡荠藤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年伙单,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哈肖。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡吻育,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出淤井,到底是詐尸還是另有隱情布疼,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布币狠,位于F島的核電站游两,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏总寻。R本人自食惡果不足惜器罐,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一梢为、第九天 我趴在偏房一處隱蔽的房頂上張望渐行。 院中可真熱鬧,春花似錦铸董、人聲如沸祟印。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蕴忆。三九已至,卻和暖如春悲幅,著一層夾襖步出監(jiān)牢的瞬間套鹅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工汰具, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留卓鹿,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓留荔,卻偏偏與公主長得像吟孙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359

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