前言:
這幾篇文章是小編在鑽研《Effective Objective-C 2.0》的知識產出,其中包含作者和小編的觀點,以及小編整理的一些demo。希望能幫助大家以簡潔的文字快速領悟原作者的精華。
在這裡,QiShare團隊向原作者Matt Galloway表達誠摯的敬意。
文章目錄如下:
iOS 編寫高質量Objective-C程式碼(一)
iOS 編寫高質量Objective-C程式碼(二)
iOS 編寫高質量Objective-C程式碼(三)
iOS 編寫高質量Objective-C程式碼(四)
iOS 編寫高質量Objective-C程式碼(五)
iOS 編寫高質量Objective-C程式碼(六)
iOS 編寫高質量Objective-C程式碼(七)
iOS 編寫高質量Objective-C程式碼(八)
本篇的主題是:協議與分類(protocol
& category
)
先簡單介紹一下今天的主角:協議 與 分類
- 協議(
protocol
):OC中的協議與Java裡的介面(interface
)類似,OC不支援多繼承,但是可以通過協議來實現委託模式。 - 分類(
category
):分類可以為既有類新增新的功能。分類是把“雙刃劍”,用得好可以發揮OC的高動態性
,用的不好則會留下很多坑。而本文就是對category
的一些研究。
一、通過委託與資料來源協議進行物件間通訊
委託模式(又稱代理):某物件將一類方法(任務)交給另一個物件幫忙完成。 類似於:老闆把一類任務交給某個leader去完成。
舉例來說,當某物件要從另一個物件獲取資料時,就可以使用委託模式。通過實現協議來獲取資料,這樣的協議一般被稱為“資料來源協議”(Data Source Protocol
)。類似於UITableView
的UITableViewDataSource
。
再舉例來說,當一個物件要有一些事件響應時,就可以使用委託模式。通過實現一個協議(一般稱為delegate
),讓代理物件幫助該物件處理事件響應。類似於UITableView
的UITableViewDelegate
。
請看圖解:
- 好處:通過協議來降低程式碼的耦合性。(解耦)
必要的時候協議還可以替代繼承。因為遵守同一個協議的類可以有很多,不一定要繼承。
百說不如一Demo:這是小編整理的關於Button動畫的例子
- QiCircleAnimationView.h:
@class QiAnimationButton;
@protocol QiAnimationButtonDelegate <NSObject>
@optional
- (void)animationButton:(QiAnimationButton *)button willStartAnimationWithCircleView:(QiCircleAnimationView *)circleView;
- (void)animationButton:(QiAnimationButton *)button didStartAnimationWithCircleView:(QiCircleAnimationView *)circleView;
- (void)animationButton:(QiAnimationButton *)button willStopAnimationWithCircleView:(QiCircleAnimationView *)circleView;
- (void)animationButton:(QiAnimationButton *)button didStopAnimationWithCircleView:(QiCircleAnimationView *)circleView;
- (void)animationButton:(QiAnimationButton *)button didRevisedAnimationWithCircleView:(QiCircleAnimationView *)circleView;
@end
@interface QiAnimationButton : UIButton
@property (nonatomic, weak) id<QiAnimationButtonDelegate> delegate;
- (void)startAnimation;//!< 開始動畫
- (void)stopAnimation;//!< 結束動畫
@end
複製程式碼
- QiAnimationButton.m中: 就可以通過這樣的方式回撥
if ([self.delegate respondsToSelector:@selector(animationButton:willStartAnimationWithCircleView:)]) {
[self.delegate animationButton:self willStartAnimationWithCircleView:_circleView];
}
/* .... */
if ([self.delegate respondsToSelector:@selector(animationButton:didStartAnimationWithCircleView:)]) {
[self.delegate animationButton:self didStartAnimationWithCircleView:_circleView];
}
複製程式碼
這種形式的例子很多,所以,就會寫出很多類似於這樣格式的程式碼:
if ([self.delegate respondsToSelector:@selector(xxxFunction)]) {
[self.delegate xxxFunction];
}
複製程式碼
解釋:因為該協議內的方法是@optional
修飾的,所以遵守協議的Class
可以選擇性地
實現協議裡的方法。因此,代理物件在呼叫回撥方法時,需要先檢查一下Class
有沒有實現該協議裡的方法?如果實現了,就回撥;如果沒有實現,就接著往下走。
考慮效能優化:
大家設想一下,這樣一個場景:回撥方法被頻繁回撥。也就是說,某回撥方法被呼叫的頻率很高。那麼每呼叫一次回撥方法都要去查一下
Class
有沒有實現該回撥方法。所以效能上會變差。
解決方案:實現一個含有位段的結構體,把委託物件能否響應某個協議方法的資訊快取起來,以優化程式執行效率。
百說不如一Demo,下面請看小編整理的Demo~
- 宣告一個結構體
DelegateFlags
:
@interface QiAnimationButton () {
struct DelegateFlags {
int doWillStartAnimation : 1;
int doDidStartAnimation : 1;
int doWillStopAnimation : 1;
int doDidStopAnimation : 1;
int doDidRevisedAnimation : 1;
};
}
複製程式碼
- 宣告一個屬性:
@property (nonatomic, assign) struct DelegateFlags delegateFlags;
複製程式碼
- 重寫
delegate
的set
方法:將是否實現該協議方法的資訊快取起來
- (void)setDelegate:(id<QiAnimationButtonDelegate>)delegate {
_delegate = delegate;
_delegateFlags.doWillStartAnimation = [delegate respondsToSelector:@selector(animationButton:willStartAnimationWithCircleView:)];
_delegateFlags.doDidStartAnimation = [delegate respondsToSelector:@selector(animationButton:didStartAnimationWithCircleView:)];
_delegateFlags.doWillStopAnimation = [delegate respondsToSelector:@selector(animationButton:willStopAnimationWithCircleView:)];
_delegateFlags.doDidStopAnimation = [delegate respondsToSelector:@selector(animationButton:didStopAnimationWithCircleView:)];
_delegateFlags.doDidRevisedAnimation = [delegate respondsToSelector:@selector(animationButton:didRevisedAnimationWithCircleView:)];
}
複製程式碼
- 直接通過
_delegateFlags
快取的值判斷能否回撥
if (_delegateFlags.doWillStartAnimation) {
[self.delegate animationButton:self willStartAnimationWithCircleView:_circleView];
}
/* .... */
if (_delegateFlags.doDidStartAnimation) {
[self.delegate animationButton:self didStartAnimationWithCircleView:_circleView];
}
複製程式碼
二、把複雜類的實現程式碼分散到便於管理的數個分類之中
- 使用分類機制,把一些很複雜的類“瘦身”,劃分成各個易於管理的分類。
- 把私有方法作為一個單獨的分類,已隱藏實現細節。
好處:
1. 把複雜的類拆成小塊,解耦。易於維護,易於管理。
2. 便於除錯:遇到問題能快速定位是哪個分類。
小編看法:視具體情況而定,拆分的同時,也會多出很多檔案。如果一個類過於臃腫(比如有幾千行程式碼),可以考慮給他瘦身,拆分成多個分類。
三、總是為第三方分類的名稱加字首
- 分類機制最大的功能:就是為不能修改原始碼的既有類中新增新的功能。
這時候我們要:
- 在分類類名前,加上專有字首。
- 在分類方法名前,加上專有字首。
最大限度上避免重名可能帶來的bug,而且這種bug很難排查。
原因在於:分類的方法會直接新增在類中,而分類是在執行期把方法加入主類。這時候,如果出現方法重名,後一個寫入的分類方法會把前一個覆蓋掉。多次覆蓋的結果總以最後一個分類為準。所以我們要加字首,儘量避免重名帶來的bug。
四、勿在分類中宣告屬性
不要在分類中宣告屬性,但可以在**類擴充套件(extension)**中宣告屬性,這樣屬性就不會暴露在外面。
舉個例子:(類擴充套件)
// QiShare.m
@interface QiShare ()
/* 屬性可以宣告在這裡 */
@end
@implementation QiShare
/* ... */
@end
複製程式碼
-
不能在分類中直接宣告屬性。如果宣告瞭,編譯時會報如下警告:
Property 'name' requires method 'setName:' to be defined - use @dynamic or provide a method implementation in this category
解釋:分類無法合成相關的例項變數,需要開發者為該屬性實現存取方法(get和set)。因為沒有生成例項變數,set方法行不通。get方法可以返回固定值。或者使用@dynamic宣告(即不會宣告例項變數和存取方法)。 -
通過關聯物件,為分類新增屬性。(詳情見第二篇 - 第5條)
所以,
1. 建議把屬性都放在主類中。
2. 不到迫不得已,儘量不要在分類中通過關聯物件新增屬性。因為關聯物件的記憶體管理問題上很容易出錯,使用時需要重點提防。
五、使用“class-continuation分類”隱藏實現細節
這裡的“class-continuation分類” 指的就是 類擴充套件(extension)。
我們可以把一些私有的屬性宣告在類擴充套件裡,這樣在匯入.h檔案時,看不到類擴充套件宣告的屬性。 目的:把公共介面中向外暴露的內容最小化,隱藏一些屬性和實現細節。
這裡補充一個小知識點:大家都知道Objective-C,但聽說過Objective-C++嗎?
Objective-C++是Objective-C和C++的混編,編譯時會生成.mm
檔案。
這時候會遇到一個問題:因為只有類的.mm
檔案才能同時編譯OC和C++。所以,當一個類所匯入所有檔案樹中包含C++檔案,此類的.m檔案就會被編譯成.mm
檔案。
那麼,OC怎麼解決呢?用類擴充套件
。
舉個例子:
#import "OCClass.h"
#import "CppClass.cpp"
@interface OCClass () {
SomeCppClass *_cppClass;
}
@end
@implementation OCClass
/* ... */
@end
複製程式碼
這樣,.h
檔案中就沒有C++程式碼了,如果只看標頭檔案甚至都不知道底層有C++的程式碼。其實,我們的系統也是這樣做的。比如WebKit、CoreAnimation等,很多底層程式碼都是通過C++寫的。
小結:類擴充套件的應用場景 1. 向類中新增例項變數或屬性 2. 在
.h
檔案中把屬性宣告為“只讀”,而類的內部又想修改此屬性,可以在類擴充套件中重宣告為“可讀寫”。 3. 私有方法的原型可以宣告在類擴充套件裡。 4. 如果不想讓外部知道類中遵守了哪些協議,可以在類擴充套件中遵守協議。
六、通過協議提供匿名物件
- 可以通過協議提供匿名物件,例如:
id<someProtocol> delegate
。delegate物件的型別不限,只要能遵從這個協議的物件都可以。協議裡規定了物件所需要實現的方法。 - 使用匿名物件來隱藏型別名稱和類名。
- 物件只要實現協議裡的方法即可(
@optional
修飾的可以選擇性實現),其餘的實現細節都被隱藏起來了。
最後,特別緻謝:《Effective Objective-C 2.0》第四章
關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)
推薦文章: 糖是甜的,你也是: 致 async