《編寫高質量OC代碼》已順利完成一二三四五六七八篇!
附上連接:
iOS 編寫高質量Objective-C代碼(一)—— 簡介
iOS 編寫高質量Objective-C代碼(二)—— 面向對象
iOS 編寫高質量Objective-C代碼(三)—— 接口和API設計
iOS 編寫高質量Objective-C代碼(四)—— 協議與分類
iOS 編寫高質量Objective-C代碼(五)—— 內存管理機制
iOS 編寫高質量Objective-C代碼(六)—— block專欄
iOS 編寫高質量Objective-C代碼(七)—— GCD專欄
iOS 編寫高質量Objective-C代碼(八)—— 系統框架緩存
本篇的主題是:協議與分類(protocol
& category
)性能優化
先簡單介紹一下今天的主角:協議 與 分類bash
protocol
):OC中的協議與Java裏的接口(interface
)相似,OC不支持多繼承。可是能夠經過協議來實現委託模式。category
):分類能夠爲既有類添加新的功能。分類是把「雙刃劍」,用得好能夠發揮OC的高動態性
;用的很差,會留下不少坑。因此,經過這篇文章讓咱們一塊兒研究OC的一項語言特性:category
。委託模式(又稱代理):某對象將一類方法(任務)交給另外一個對象幫忙完成。 ~相似於:老闆把一類任務交給某個leader去完成。(固然多類任務就會對應多個leader去完成)。~框架
舉例來講,當某對象要從另外一個對象獲取數據時,就可使用委託模式。經過實現數據源協議來獲取數據,這種作法被稱爲「數據源協議」(Data Source Protocol
)。相似於UITableView
的UITableViewDataSource
。ide
再舉例來講,當一個對象要有一些事件響應時,就可使用委託模式。經過實現一個協議(通常稱爲delegate
),讓代理對象幫助該對象處理事件響應。相似於UITableView
的UITableViewDelegate
。性能
請看圖解:優化
百說不如一Demo:這是小編整理的關於Button動畫的例子動畫
@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
複製代碼
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];
}
複製代碼
等等等...ui
因此,就會寫出不少相似於這樣格式的代碼:this
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分類」 指的就是 類擴展(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》第四章