iOS 編寫高質量Objective-C代碼(三)—— 接口和API設計

《編寫高質量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代碼(八)—— 系統框架html


這一篇,將經過介紹OC的接口和API設計來提升Objective-C的代碼質量編程

1、用前綴避免命名空間衝突

OC裏沒有命名空間的概念(namespace)。因而,咱們須要給類加前綴,避免重名,避免發生命名衝突。固然,不只是類名,一些全局變量和方法也須要加上適當的前綴加以區分。安全

因此,咱們要:bash

  • 選擇與公司、工程相關的前綴做爲類名的前綴。框架

  • 爲了不重複引用第三方庫帶來的衝突,必要時也要爲他們加上前綴區分。編程語言

2、提供「全能初始化方法」

  • 在類中提供一個全能初始化方法,並在文檔中寫明註釋。其餘的初始化方法全調用此全能初始化方法。
  • 好處:當類的結構發生改變或初始化邏輯發生改變時,只須要改動全能初始化方法便可。

舉個例子來講:能夠看一下NSDate類中定義了一個全能初始化方法:ide

- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti NS_DESIGNATED_INITIALIZER;
複製代碼

其他的初始化方法 定義在NSDate (NSDateCreation) 分類中測試

- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
複製代碼

在官方文檔中,關於NSDate有以下說明 If you want to subclass NSDate to obtain behavior different than that provided by the private or public subclasses, you must:atom

  • Override initWithTimeIntervalSinceReferenceDate:, one of the designated initializer methods

解釋:選定一個方法做爲全能初始化方法,剩下的其他的初始化方法都調用這這個方法初始化,這樣作的好處是之後若是初始化的邏輯更改了只需更改全能初始化方法,或者即便子類覆寫的時候也只覆寫全能初始化方法~spa

3、實現 description 方法

本條寫的是經過覆寫description(或者debugDescription)方法來在NSLog打印(或者LLDB打印時)輸出更多的自定義信息。

下面舉個例子:

- (NSString *)description {

    return [NSString stringWithFormat:@"<%@: %p, %@>",
            [self class],
            self,
            @{
              @"qi": _qi,
              @"share" : _share}
            ];
}
複製代碼

4、儘可能使用不可變對象

  1. 聲明對外屬性時,儘可能使用不可變對象,同時,對外屬性聲明裏儘可能加上readonly修飾~(默認是readwrite修飾)~。這樣外部只能讀取數據而不能修改數據,保證了這個類的實例所持有的數據更加安全。~尤爲是不要把可變的collection做爲屬性公開,而是應該提供相應的方法修改可變的collection。~
  2. 若外部想修改修改對象的值有兩種途徑:
    • 提供接口方法修改
    • 使用KVC(Key-Value Coding)技術 ~這種技術容許對象的數據或屬性能夠在運行時經過其鍵名進行查找,其中,屬性的名稱即爲其值的鍵名。在靜態語言中,這樣的作法是不可能的。KVC大大的增長了設計的自由度:經過KVC,無需知道對象的類型便可訪問其屬性或數據。~

例如: 不推薦寫法:

//Animals.h
@property (nonatomic, strong) NSSet *animals;
複製代碼

應改成:

//Animals.h
@interface Animals : NSObject

@property (nonatomic, strong, readonly) NSSet *animals;

- (void)addAnimal:(NSString *)animal;
- (void)removeAnimal:(NSString *)animal;

@end


//Animals.m
@implementation Animals {
    NSMutableSet *_mutableAnimals;
}

- (NSSet *)animals {
    return [_mutableAnimals copy];
}

- (void)addAnimal:(NSString *)animal {
    [_mutableAnimals addObject:animal];
}

- (void)removeAnimal:(NSString *)animal {
    [_mutableAnimals removeObject:animal];
}
複製代碼

可是,小編認爲這樣寫當然有好處:保證了數據的安全性,但代碼量也會提高很多。因此推薦你們能夠有選擇的使用,對一些重要的類纔有使用必要。

另外,若是某屬性僅能夠在對象內部修改,則能夠在.h文件中聲明爲readonly。而後 在.m的類擴展中將屬性擴展爲readwrite屬性。

固然,師父說了:也能夠在.h文件中把屬性聲明爲readonly,在.m文件中經過實例變量修改值,當block內部修改值時,能夠用self->實例變量的方法訪問修改。~(小編測試過,確實有效。歡迎路過的大神繼續討論)~

5、使用清晰而協調的命名方式

師父語錄:「寫OC代碼像是在講故事,而讀OC代碼更像是在聽故事。」

這句話要歸功於OC清晰而協調的命名方式。

  • 首先,是駝峯式命名方法:這個和大部分編程語言都同樣。
  • 其次,也是最關鍵的方法命名。從左至右讀起來就像平常用語中的句子。

例如:咱們想給初始化一個矩形,並給他的寬和高賦值。

// C++:
Rectangle *aRectangle = new Rectangle(5.0, 10.0);

// Objective-C:
Rectangle *aRectangle = [[Rectangle alloc] initWithWidth:5.0 andHeight:10.0];
複製代碼

很顯然,OC的方法能夠很直接的看出所要傳遞的參數的具體含義,而C++的傳參就並無這麼直觀。

6、爲私有方法名加前綴

這一條:給你們參考一下咱們QiShare團隊制定的 iOS 代碼規範 QiShare更喜歡經過#pragma mark -來區分 公私有等方法。 例如:

#pragma mark - Private Functions

// code...


#pragma mark - Action functions

// code...


#pragma mark - Request functions

// code...


#pragma mark - xxxDataSource

// code...


#pragma mark - xxxDelegate

// code...

複製代碼

固然,你們也能夠根據團隊本身定製規範。

7、理解 Objective-C 錯誤模型

不少語言都有異常處理機制,Objective-C也不例外。@throw 可是, 注意:OC裏拋異常可能會致使內存泄漏 注意:OC裏拋異常可能會致使內存泄漏 注意:OC裏拋異常可能會致使內存泄漏 解釋:OC裏的ARC機制(Automatic Reference Counting)在默認狀況下是「無異常安全」。簡單來講,一旦拋出異常,對象就沒法正常自動釋放了。 因此,

  1. 異常只用於處理嚴重的錯誤(fatal error,致命錯誤)
  2. 對於一些不那麼嚴重的錯誤(nonfatal error,非致命錯誤),有兩種解決方案:
    • 讓對象返回nil或者0(例如:初始化的參數不合法,方法返回nil或0)
    • 使用NSError

8、理解 NSCopying協議

在iOS開發中,使用對象時常常須要拷貝它,這時咱們會經過copy/mutableCopy方法完成。 若是咱們想讓本身的類支持拷貝,那就必需要實現NSCopying協議,該協議只有一個方法:

- (id)copyWithZone:(nullable NSZone *)zone;
複製代碼

固然,若是要求返回對象是可變的那就要實現NSMutableCopying協議,對應方法:

- (id)mutableCopyWithZone:(nullable NSZone *)zone;
複製代碼

同時,在拷貝對象時,要注意是執行淺拷貝仍是深拷貝

那麼引出了一個概念:什麼是深拷貝?什麼是淺拷貝?

  • 深拷貝:內容拷貝(既拷貝新的指針又拷貝出新的Object
  • 淺拷貝:指針拷貝(僅拷貝新的指針指向原來的Object

這裏有張很經典的圖解:

左邊淺拷貝,右邊深拷貝
深拷貝在拷貝對象時,會將指針所指的底層數據也拷貝一份。而 淺拷貝只是建立了一個新的指針指向要拷貝的內容。通常狀況下,儘可能使用淺拷貝。 此外,還有一個注意點: [NSMutableArray copy] 拷貝出 => NSArray (不可變) [NSArray mutableCopy] 拷貝出 => NSMutableArray(可變) 這種操做能夠在可變版本和不可變版本間切換。

說太多,不如給一個Demo~

  • 下面請看小編準備的NSCopying協議相關的小Demo:

QiShareMember.h:

@interface QiShareMember : NSObject <NSCopying>

@property (nonatomic, copy, readonly) NSString *name; //!< 姓名
@property (nonatomic, copy, readonly) NSString *sex; //!< 性別
@property (nonatomic, assign, readonly) NSUInteger age; //!< 年齡

//! 初始化方法
- (instancetype)initWithName:(NSString *)name andSex:(NSString *)sex andAge:(NSUInteger)age;

- (void)addFriend:(QiShareMember *)friend;
- (void)removeFriend:(QiShareMember *)friend;

@end
複製代碼

QiShareMember.m:

@implementation QiShareMember {
    
    NSMutableSet *_friends;
}

- (instancetype)initWithName:(NSString *)name andSex:(NSString *)sex andAge:(NSUInteger)age {
    
    if (self = [super init]) {
        
        _name = [name copy];
        _sex = [sex copy];
        _age = age;
        _friends = [NSMutableSet new];
    }
    
    return self;
}

- (void)addFriend:(QiShareMember *)friend {
    
    [_friends addObject:friend];
}

- (void)removeFriend:(QiShareMember *)friend {
    
    [_friends removeObject:friend];
}

- (id)copyWithZone:(NSZone *)zone {
    
    QiShareMember *copy = [[[self class] allocWithZone:zone] initWithName:_name andSex:_sex andAge:_age];
    copy->_friends = [_friends mutableCopy]; //!< 注意friends只是一個實例變量不是一個屬性,因此不能用點語法
    
    return copy;
}

@end
複製代碼

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

相關文章
相關標籤/搜索