iOS 編寫高質量Objective-C代碼(四)—— 協議與分類

《編寫高質量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

1、經過委託與數據源協議進行對象間通訊

委託模式(又稱代理):某對象將一類方法(任務)交給另外一個對象幫忙完成。 ~相似於:老闆把一類任務交給某個leader去完成。(固然多類任務就會對應多個leader去完成)。~框架

舉例來講,當某對象要從另外一個對象獲取數據時,就可使用委託模式。經過實現數據源協議來獲取數據,這種作法被稱爲「數據源協議」(Data Source Protocol)。相似於UITableViewUITableViewDataSourceide

再舉例來講,當一個對象要有一些事件響應時,就可使用委託模式。經過實現一個協議(通常稱爲delegate),讓代理對象幫助該對象處理事件響應。相似於UITableViewUITableViewDelegate性能

請看圖解:優化

  • 好處:經過協議來下降代碼的耦合性。(解耦) 必要的時候協議還能夠替代繼承。由於遵照同一個協議的類能夠有不少,不必定要繼承。

百說不如一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];
}
複製代碼

等等等...ui

因此,就會寫出不少相似於這樣格式的代碼:this

if ([self.delegate respondsToSelector:@selector(xxxFunction)]) {
    [self.delegate xxxFunction];
}
複製代碼

解釋:由於該協議內的方法是@optional修飾的,因此遵照協議的Class能夠選擇性地實現協議裏的方法。所以,代理對象在調用回調方法時,須要先檢查一下Class有沒有實現該協議裏的方法?若是實現了,就回調;若是沒有實現,就接着往下走。

考慮性能優化:

你們設想一下,這樣一個場景:回調方法被頻繁回調。也就是說,某回調方法被調用的頻率很高。那麼每調用一次回調方法都要去查一下Class有沒有實現該回調方法。因此性能上會變差。

解決方案:實現一個含有位段的結構體,把委託對象可否響應某個協議方法的信息緩存起來,以優化程序執行效率。

百說不如一Demo,下面請看小編整理的Demo~

  1. 聲明一個結構體DelegateFlags
@interface QiAnimationButton () {
    
    struct DelegateFlags {
        int doWillStartAnimation : 1;
        int doDidStartAnimation : 1;
        int doWillStopAnimation : 1;
        int doDidStopAnimation : 1;
        int doDidRevisedAnimation : 1;
    };
}
複製代碼
  1. 聲明一個屬性:
@property (nonatomic, assign) struct DelegateFlags delegateFlags;
複製代碼
  1. 重寫delegateset方法:將是否實現該協議方法的信息緩存起來
- (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:)];
}
複製代碼
  1. 直接經過_delegateFlags緩存的值判斷可否回調
if (_delegateFlags.doWillStartAnimation) {
   [self.delegate animationButton:self willStartAnimationWithCircleView:_circleView];
}

/* .... */

if (_delegateFlags.doDidStartAnimation) {
   [self.delegate animationButton:self didStartAnimationWithCircleView:_circleView];
}
複製代碼

2、把複雜類的實現代碼分散到便於管理的數個分類之中

  • 使用分類機制,把一些很複雜的類「瘦身」,劃分紅各個易於管理的分類。
  • 把私有方法做爲一個單獨的分類,已隱藏實現細節。

好處: 1. 把複雜的類拆成小塊,解耦。易於維護,易於管理。 2. 便於調試:遇到問題能快速定位是那個分類。

小編見解:視具體狀況而定,拆分的同時,也會多出不少文件。若是一個類過於臃腫(好比有幾千行代碼),能夠考慮給他瘦身,拆分紅多個分類。

3、老是爲第三方分類的名稱加前綴

  • 分類機制最大的功能:就是爲不能修改源碼的既有類中添加新的功能。

這時候咱們要:

  • 在分類類名前,加上專有前綴。
  • 在分類方法名前,加上專有前綴。

最大限度上避免重名可能帶來的bug,並且這種bug很難排查。

緣由在於:分類的方法會直接添加在類中,而分類是在運行期把方法加入主類。這時候,若是出現方法重名,後一個寫入的分類方法會把前一個覆蓋掉。屢次覆蓋的結果總以最後一個分類爲準。因此咱們要加前綴,儘可能防止出現重名帶來的bug。

4、勿在分類中聲明屬性

不要在分類中聲明屬性,但能夠在**類擴展(extension)**中聲明屬性,這樣屬性就不會暴露在外面。

舉個例子:(類擴展)

// QiShare.m
@interface QiShare ()
/* 屬性能夠聲明在這裏 */
@end


@implementation QiShare
/* ... */
@end
複製代碼
  1. 不能在分類中直接聲明屬性。若是聲明瞭,編譯時會報以下警告: Property 'name' requires method 'setName:' to be defined - use @dynamic or provide a method implementation in this category 解釋:分類沒法合成相關的實例變量,須要開發者爲該屬性實現存取方法(get和set)。由於沒有生成實例變量,set方法行不通。get方法能夠返回固定值。或者使用@dynamic聲明(即不會聲明實例變量和存取方法)。

  2. 經過關聯對象,爲分類添加屬性。(詳情見第二篇 - 第5條)

因此, 1. 建議把屬性都放在主類中。 2. 不到無可奈何,儘可能不要在分類中經過關聯對象添加屬性。由於關聯對象的內存管理問題上很容易出錯,使用時須要重點提防。

5、使用「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. 若是不想讓外部知道類中遵照了哪些協議,能夠在類擴展中遵照協議。

6、經過協議提供匿名對象

  1. 能夠經過協議提供匿名對象,例如:id<someProtocol> delegate。delegate對象的類型不限,只要能聽從這個協議的對象均可以。協議裏規定了對象所須要實現的方法。
  2. 使用匿名對象來隱藏類型名稱和類名。
  3. 對象只要實現協議裏的方法便可(@optional修飾的能夠選擇性實現),其他的實現細節都被隱藏起來了。

最後,特別緻謝:《Effective Objective-C 2.0》第四章

相關文章
相關標籤/搜索