iOS編寫高質量Objective-C程式碼(四)
《編寫高質量OC程式碼》已經順利完成一二三四五篇!
附上鍊接:
iOS 編寫高質量Objective-C程式碼(一)
iOS 編寫高質量Objective-C程式碼(二)
iOS 編寫高質量Objective-C程式碼(三)
iOS 編寫高質量Objective-C程式碼(四)
iOS 編寫高質量Objective-C程式碼(五)
本篇的主題是:協議與分類(protocol
& category
)
先簡單介紹一下今天的主角:協議 與 分類
- 協議(
protocol
):OC中的協議與Java裡的介面(interface
)類似,OC不支援多繼承。但是可以通過協議來實現委託模式。 - 分類(
category
):分類可以為既有類新增新的功能。分類是把“雙刃劍”,用得好可以發揮OC的高動態性
;用的不好,會留下很多坑。所以,通過這篇文章讓我們一起研究OC的一項語言特性:category
。
一、通過委託與資料來源協議進行物件間通訊
委託模式(又稱代理):某物件將一類方法(任務)交給另一個物件幫忙完成。
~類似於:老闆把一類任務交給某個leader去完成。(當然多類任務就會對應多個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;//!< 結束動畫
- (void)reverseAnimation;//!< 最後的修改動畫
@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
檔案。
這時候會遇到一個問題:只要匯入含有C++的.h
,都會編譯成.mm
檔案。因為只有.mm
檔案才能同時編譯OC和C++。
那麼,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》第四章
相關文章
- iOS 編寫高質量Objective-C程式碼(四)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(一)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(三)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(二)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(八)iOSObjectC程式
- iOS編寫高質量Objective-C程式碼(二)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(五)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(六)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(七)iOSObjectC程式
- iOS編寫高質量Objective-C程式碼(六)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(一)—— 簡介iOSObjectC程式
- iOS 有效編寫高質量Objective-C方法(一)iOSObject
- iOS-有效編寫高質量Objective-C方法-三iOSObject
- [轉]高質量JAVA程式碼編寫規範Java
- 怎樣編寫高質量的java程式碼Java
- 編寫高質量的程式碼,從命名入手
- [編寫高質量iOS程式碼的52個有效方法](九)塊(block)iOSBloC
- 編寫高質量程式碼
- 如何編寫高質量的C#程式碼(一)C#
- 編寫高質量iOS與OS X程式碼的52個有效方法(一)iOS
- 編寫高質量iOS與OS X程式碼的52個有效方法(五)iOS
- 編寫高質量iOS與OS X程式碼的52個有效方法(二)iOS
- 編寫高質量iOS有效方法總結(一)iOS
- 如何編寫高質量和可維護的程式碼
- [編寫高質量iOS程式碼的52個有效方法](七)記憶體管理(上)iOS記憶體
- [編寫高質量iOS程式碼的52個有效方法](八)記憶體管理(下)iOS記憶體
- [編寫高質量iOS程式碼的52個有效方法](十)Grand Central Dispatch(GCD)iOSGC
- 我們應該如何編寫高質量的前端程式碼前端
- 藉助 SublimeLinter 編寫高質量的 JS & CSS 程式碼JSCSS
- 藉助SublimeLinter編寫高質量的JavaScript & CSS程式碼JavaScriptCSS
- 編寫高質量程式碼的思考
- 《Effective JavaScript 編寫高質量JavaScript程式碼的68個有效方法》JavaScript
- 如何書寫高質量的jQuery程式碼jQuery
- 編寫靈活、穩定、高質量的HTML程式碼的規範HTML
- 編寫靈活、穩定、高質量的CSS程式碼的規範CSS
- iOS-《編寫高質量程式碼》筆記-第一章iOS筆記
- 提高程式碼質量:如何編寫函式函式