iOS開發規範篇:清晰的初始化方法

花丶滿樓發表於2019-01-26

@(iOS開發學習)[溫故而知新]

[TOC]

一、Objective-C基本套路

日常開發中遇到的問題:

在平常的專案開發中,經常會遇到多人同時開發一個需求的場景。同事A提供了自定義初始化方法,但是同事B卻呼叫了預設的初始化方法,因為同事A在自定義初始化方法中做了一些特殊操作,導致同事B使用預設初始化方法卻沒有達到預期的效果,然後又浪費了很多精力與同事A進行溝通查詢問題。


幾乎大多數程式設計師都是與團隊內的其他成員合作完成一個專案,即使是自己獨立開發一個專案,一個模組呼叫另外一個模組,都需要一個清晰明確的介面規範。當面對多個初始化方法時,外部呼叫者可能手無足措,不知道哪一個才是正確的初始化方法。為此蘋果提供了兩個關鍵字:NS_UNAVAILABLENS_DESIGNATED_INITIALIZER來幫助我們約束物件的初始化方法,使得介面描述更加清晰。

  • NS_DESIGNATED_INITIALIZER用來將修飾的方法標記為指定構造器

  • NS_UNAVAILABLE禁止使用某個初始化方法

一般都希望外部呼叫介面的時候,傳入一些基本的引數用來初始化。而不希望使用預設的初始化方法,因此我們可以這麼做:

@interface Person : NSObject@property (nonatomic, strong) NSString *name;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithName:(NSString*)name NS_DESIGNATED_INITIALIZER;
@end@implementation Person- (instancetype)initWithName:(NSString *)name {
if ( self = [super init] ) {
self.name = name;

} return self;

}@end複製程式碼

當建立一個Person物件的時候,不能使用NS_UNAVAILABLE修飾的[Person new][[Person alloc]init]方法,而應該使用NS_DESIGNATED_INITIALIZER修飾的- (instancetype)initWithName:(NSString*)name方法。

// Xcode報錯:'new' is unavailablePerson* person1 = [Person new];
// Xcode報錯:'init' is unavailablePerson* person2 = [[Person alloc]init];
// 正確Person* person3 = [[Person alloc]initWithName:@"XiaoMing"];
複製程式碼

二、Objective-C細枝末節

當想讓呼叫者呼叫自己的構造方法的時候,就可以在.h檔案中將自己的構造方法使用NS_DESIGNATED_INITIALIZER修飾


當不想讓呼叫者呼叫父類的建構函式的時候,就可以在.h檔案中將父類的構造方法使用NS_UNAVAILABLE修飾


如果子類實現了NS_DESIGNATED_INITIALIZER修飾的指定初始化方法,沒有使用NS_UNAVAILABLE修飾父類的初始化方法。則需要在子類重寫父類的指定初始化方法,並且在裡面呼叫子類自己的指定初始化方法。因為如果一個類的方法被 NS_DESIGNATED_INITIALIZED 修飾,則改方法變成指定構造方法,從父類繼承來的指定構造方法則變成便利初始化方法。子類沒有重寫父類的指定初始化方法會報類似警告:

1、⚠️:Method override for the designated initializer of the superclass '-init' not found(沒有找到父類的指定初始化方法)

2、⚠️:Convenience initializer missing a 'self' call to another initializer(便利初始化方法需要呼叫另外一個初始化方法)

重寫父類的指定初始化方法後,不能呼叫super相關的方法。否則會報類似警告:

⚠️:Convenience initializer should not invoke an initializer on 'super'(便利初始化方法不能呼叫super初始化方法)


避免使用new建立物件,從安全和設計角度來說我們應該對初始化所有屬性,提高程式的健壯性和複用性。

不論是何種情況,在類中至少包含一個建構函式是一種很好的程式設計實踐,如果類中有屬性,好的實踐往往是初始化這些屬性。——以上摘自《The Object-Oriented Thought Process》 by Matt Weisfeld


術語區分構造方法 vs 初始化方法

嚴格意義上Objective-c是沒有建構函式的,我們所說的都是初始化方法,建立物件(alloc)之後呼叫例項的初始化方法initWithXXX

構造方法的寫法:類名(引數列表...)

構造方法是一種特殊的方法,它是一個與類同名且返回值型別為同名類型別的方法。物件的建立就是通過構造方法來完成,其功能主要是完成物件的初始化。當類例項化一個物件時會自動呼叫構造方法。構造方法和其他方法一樣也可以過載。


Objective-C與swift的初始化順序的區別:

Objective-C先呼叫父類的初始化方法,然後初始自己的成員變數

swift先初始化自己的成員變數,然後在呼叫父類的初始化方法

三、Swift中的指定構造方法和便利初始化方法

class People { 
var name: String? // 在swift中屬性不是可選型別的都必須初始化 var age: Int // 指定初始化方法前面不需要新增修飾 init() {
age = 0
} // 便利初始化方法前面需要新增convenience修飾 convenience init(name: String) {
self.init() self.name = name
}
}class Man: People {
var mustacheLength: Int // 子類重寫父類的指定初始化方法,需要使用override修飾 override init() {
self.mustacheLength = 0
} // 便利初始化方法一:內部呼叫指定初始化方法 convenience init(mustacheLength: Int) {
self.init() self.mustacheLength = mustacheLength // 修改父類的屬性值必須在子類的便利初始化方法內部呼叫完指定初始化方法後 self.age = 1
} // 便利初始化方法二:內部呼叫其他便利初始化方法,但是最後一個便利初始化方法內部還是要呼叫指定初始化方法 convenience init(mustacheLength: Int, name: String = "") {
self.init(mustacheLength: mustacheLength)
}
}複製程式碼

3.1、為何要初始化?

  • 系統要求儲存屬性必須初始化
  • 結構體系統預設會新增初始化方法,當然自己也可以自定義

3.2、類初始化的幾種方法

3.2.1、Designated

  • 可選值可以不用初始化,如果不初始化值,系統預設用nil初始化它。
  • 如果類中含有非可選的儲存屬性並且沒有預設值,則必須實現指定初始化方法,並且初始化該屬性。
  • 如果子類沒有自己的初始化方法,系統預設使用父類的初始化方法,一旦有了自己的初始化方法,或者重寫了父類的初始化方法,則父類的所有初始化不能被子類呼叫。
  • 你可以給子類新增和父類相同的初始化方法,但需要加上override修飾。

3.2.2、convenience

  • 在同一個類,使用convenience修飾的初始化方法必須呼叫一個其他初始化方法。
  • convenience必須最終呼叫一個指定的初始化方法。
  • 重寫父類的convenience修飾的方便初始化方法,不需要加override關鍵字。

3.2.3、required

  • 子類必須重寫父類用required修飾的方法
  • 可以和convenience組合使用

參考資料

NS_UNAVAILABLE 與 NS_DESIGNATED_INITIALIZER

Swift 初始化(Initialization)

Swift-init初始化方法

來源:https://juejin.im/post/5c4c64c3f265da611f0809f1

相關文章