深刻了解 iOS 的初始化

初始化

在 iOS 裏面,不管是 Objective-C 仍是 Swift,類(結構體、枚舉)的初始化都有必定的規則要求,只不過在 Objective-C 中會比較寬鬆,若是不按照規則也不會報錯,但會存在隱患,而在 Swift 則須要嚴格按照規則要求代碼才能編譯經過,極大提升了代碼的安全性。html

類(結構體、枚舉)的初始化有兩種初始化器(初始化方法):指定初始化器(Designated Initializers )、便利初始化器(Convenience Initializers)swift

Designated Initializers

指定初始化器是類(結構體、枚舉)的主初始化器,類(結構體、枚舉)初始化的時候必須調用自身或者父類的指定初始化器。一個類(結構體、枚舉)能夠有多個指定初始化器,做用是表明從不一樣的源進行初始化。一個類(結構體、枚舉)除非有多種不一樣的源進行初始化,不然不建議建立多個指定初始化器。在 iOS 裏,視圖控件類,若是:UIViewUIViewController就有兩個指定初始化器,分別表明從代碼初始化、從Nib初始化安全

Convenience Initializers

便利初始化器是類(結構體、枚舉)的次要初始化器,做用是使類(結構體、枚舉)在初始化時更方便設置相關的屬性(成員變量)。既然便利初始化器是爲了便利,那麼一個類(結構體、枚舉)就能夠有多個便利初始化器,這些便利初始化器裏面最後都須要調用自身的指定初始化器ide

核心規則

iOS 的初始化最核心兩條的規則:ui

  • 必須至少有一個指定初始化器,在指定初始化器裏保證全部非可選類型屬性都獲得正確的初始化(有值)
  • 便利初始化器必須調用其餘初始化器,使得最後確定會調用指定初始化器

Initialization

全部的其餘規則都根據這兩條規則而展開,只是 Objective-C 沒有那麼多安全檢查,顯得比較隨意、寬鬆,而 Swift 則有一堆的限制。atom

Objective-C

Objective-C 在初始化時,會自動給每一個屬性(成員變量)賦值爲 0 或者 nil,沒有強制要求額外爲每一個屬性(成員變量)賦值,方便的同時也缺乏了代碼的安全性。spa

Objective-C 中的指定初始化器會在後面被NS_DESIGNATED_INITIALIZER修飾,如下爲NSObjectUIView的指定初始化器code

// NSObject
@interface NSObject <NSObject> 

- (instancetype)init
#if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER
    NS_DESIGNATED_INITIALIZER
#endif
    ;
@end
  
  
// UIView
@interface UIView : UIResponder

- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;

@end
複製代碼

在 Objective-C 裏面,全部類都繼承自NSObject。當自定義一個類的時候,要麼直接繼承自NSObject,要麼繼承自UIView或者其餘類。cdn

不管繼承自什麼類,都常常須要新的初始化方法,而這個新的初始化方法其實就是新的指定初始化器。若是存在一個新的指定初始化器,那麼原來的指定初始化器就會自動退化成便利初始化器。爲了遵循必需要調用指定初始化器的規則,就必須重寫舊的定初始化器,在裏面調用新的指定初始化器,這樣就能確保全部屬性(成員變量)被初始化htm

根據這條規則,能夠從NSObjectUIView中看出,因爲UIView擁有新的指定初始化器-initWithFrame:,致使父類NSObject的指定初始化器-init退化成便利初始化器。因此當調用[[UIView alloc] init]時,-init裏面必然調用了-initWithFrame:

當存在一個新的指定初始化器的時候,推薦在方法名後面加上NS_DESIGNATED_INITIALIZER,主動告訴編譯器有一個新的指定初始化器,這樣就可使用 Xcode 自帶的Analysis功能分析,找出初始化過程當中可能存在的漏洞

@interface MyView : UIView

@property (nonatomic, strong) NSString *name;

// 推薦加上NS_DESIGNATED_INITIALIZER
- (instancetype)initWithFrame:(CGRect)frame name:(NSString *)name NS_DESIGNATED_INITIALIZER;

@end


@implementation MyView

// 初始化時加入參數name,這個方法已經成爲新的指定初始化器
- (instancetype)initWithFrame:(CGRect)frame name:(NSString *)name {
    if (self = [super initWithFrame:frame]) {
        self.name = name;
    }
    return self;
}

// 舊的指定初始化器就自動退化成便利初始化器,必須在裏面調用新的指定初始化器
- (instancetype)initWithFrame:(CGRect)frame {
    return [self initWithFrame:frame name:@"Daniels"];
}

// 舊的指定初始化器就自動退化成便利初始化器,必須在裏面調用新的指定初始化器
- (instancetype)initWithCoder:(NSCoder *)coder {
    // 這裏的實現是僞代碼,只是爲了知足規則
    return [self initWithFrame:CGRectNull name:@"Daniels"];
}

@end
複製代碼

若是不想去重寫舊的指定初始化器,但又不想存在漏洞和隱患,那麼可使用NS_UNAVAILABLE把舊的指定初始化器都廢棄,外界就沒法調用舊的指定初始化器

@interface MyView : UIView

@property (nonatomic, strong) NSString *name;



// 推薦加上NS_DESIGNATED_INITIALIZER
- (instancetype)initWithFrame:(CGRect)frame name:(NSString *)name NS_DESIGNATED_INITIALIZER;

// 廢棄舊的指定初始化器
- (instancetype)init NS_UNAVAILABLE;

// 廢棄舊的指定初始化器
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;

// 廢棄舊的指定初始化器
- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;

@end


@implementation MyView

// 初始化時加入參數name,這個方法已經成爲新的指定初始化器
- (instancetype)initWithFrame:(CGRect)frame name:(NSString *)name {
    if (self = [super initWithFrame:frame]) {
        self.name = name;
    }
    
    return self;
}


@end
複製代碼

固然,一個新的類也能夠不增長新的初始化方法,在 Objective-C 中,子類會直接繼承父類全部的初始化方法

Swift

在 Swift 中,初始化器的規則嚴格且複雜,目的就是爲了使代碼更加安全,若是不符合規則,會直接報錯,經常會讓剛接手 Swift 或者一直對 iOS 的初始化沒有深刻理解的人很頭疼。其實核心規則仍是同樣,只要理解了各個規則的含義和做用,寫起來仍是沒有壓力。

從 iOS 初始化的核心規則展開而來,Swift 多了一些規則:

  • 初始化的時候須要保證類(結構體、枚舉)的全部非可選類型屬性都會有值,不然會報錯。
  • 在沒有給全部非可選類型屬性賦值(初始化完成)以前,不能調用self相關的任何東西,例如:調用實例屬性,調用實例方法。

不存在繼承

這種狀況處理就十分簡單,本身裏面的init方法就是它的指定初始化器,並且能夠隨意建立多個它的指定初始化器。若是須要建立便利初始化器,則在方法名前面加上convenience,且在裏面必須調用其餘初始化器,使得最後確定調用指定初始化器

class Person {

    var name: String

    var age: Int

    // 能夠存在多個指定初始化器
    init(name: String, age: Int) {
        self.name = name;
        self.age = age;
    }

    // 能夠存在多個指定初始化器
    init(age: Int) {
        self.name = "Daniels";
        self.age = age;
    }

    // 便利初始化器
    convenience init(name: String) {
        // 必需要調用本身的指定初始化器
        self.init(name: name, age: 18)
        // 必須在初始化完成後才能調用實例方法
        jump()
    }
  
    func jump() {

    }
}
複製代碼

存在繼承

若是子類沒有新的非可選類型屬性,或者保證全部非可選類型屬性都已經有默認值,則能夠直接繼承父類的指定初始化器和便利初始化器

class Student: Person {

    var score: Double = 100
  
}
複製代碼

若是子類有新的非可選類型屬性,或者沒法保證全部非可選類型屬性都已經有默認值,則須要新建立一個指定初始化器,或者重寫父類的指定初始化器

  • 新建立一個指定初始化器,會覆蓋父類的指定初始化器,須要先給當前類全部非可選類型屬性賦值,而後再調用父類的指定初始化器
  • 重寫父類的指定初始化器,須要先給當前類全部非可選類型屬性賦值,而後再調用父類的指定初始化器
  • 在保證子類有指定初始化器,才能建立便利初始化器,且在便利初始化器裏面必須調用指定初始化器
class Student: Person {

    var score: Double
		
    // 新的指定初始化器,若是有新的指定初始化器,就不會繼承父類的全部初始化器,除非重寫
    init(name: String, age: Int, score: Double) {
        self.score = score
        super.init(name: name, age: age)
    }
  
    // 重寫父類的指定初始化器,若是不重寫,則子類不存在這個方法
    override init(name: String, age: Int) {
        score = 100
        super.init(name: name, age: age)
    }
  
  
    // 便利初始化器
    convenience init(name: String) {
        // 必需要調用本身的指定初始化器
        self.init(name: name, age: 10, score: 100)
    }
}
複製代碼

須要注意的是,若是子類重寫父類全部指定初始化器,則會繼承父類的便利初始化器。緣由也是很簡單,由於父類的便利初始化器,依賴於本身的指定初始化器

Failable Initializers

在 Swift 中能夠定義一個可失敗的初始化器(Failable Initializers),表示在某些狀況下會建立實例失敗。

只有在表示建立失敗的時候纔有返回值,而且返回值爲nil

子類能夠把父類的可失敗的初始化器重寫爲不可失敗的初始化器,但不能把父類的不可失敗的初始化器重寫爲可失敗的初始化器

class Animal {
    
    let name: String
    // 可失敗的初始化器,若是把 ! 換成 ?,則爲隱式的可失敗的初始化器
    init?(name: String) {
        if name.isEmpty {
            return nil
        }
        self.name = name
    }
}

class Dog: Animal {

    override init(name: String) {
        if name.isEmpty {
            super.init(name: "旺財")!
        } else {
            super.init(name: name)!
        }
    }
}
複製代碼

Required Initializers

在 Swift 中,可使用required修飾初始化器,來指定子類必須實現該初始化器。須要注意的是,若是子類能夠直接繼承父類的指定初始化器和便利初始化器,因此也就能夠不用額外實現required修飾的初始化器

子類實現該初始化器時,也必須加上required修飾符,而不是override

class MyView: UIView {

    var name: String


    init(frame: CGRect, name: String) {
        self.name = name;
        super.init(frame: frame)
    }

    // 必須實現此初始化器,但因爲是可失敗的初始化器,因此裏面能夠不作具體實現
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
複製代碼

總結

iOS 的初始化最核心兩條的規則:

  • 必須至少有一個指定初始化器,在指定初始化器裏保證全部非可選類型屬性都獲得正確的初始化(有值)
  • 便利初始化器必須調用其餘初始化器,使得最後確定會調用指定初始化器

展開而來的多條規則:

  • 不管在 Objective-C 仍是 Swift 中,均可以有多個指定初始化器和多個便利初始化器。若是不是能夠從多個不一樣的源初始化,最好只建立一個指定初始化器
  • 不管在 Objective-C 仍是 Swift 中,都須要在便利初始化器中調用指定初始化器
  • 在 Objective-C 中,初始化的時候不須要保證全部屬性(成員變量)都有值
  • 在 Objective-C 中,若是存在一個新的指定初始化器,那麼原來的指定初始化器就會自動退化成便利初始化器。必須重寫舊的定初始化器,在裏面調用新的指定初始化器
  • 在 Swift 中,初始化的時候須要保證類(結構體、枚舉)的全部非可選類型屬性都會有值
  • 在 Swift 中,必須在初始化完成後才能調用實例屬性,調用實例方法
  • 在 Swift 中,若是存在繼承,而且子類有新的非可選類型屬性,或者沒法保證全部非可選類型屬性都已經有默認值,那麼就須要新建立一個指定初始化器,或者重寫父類的指定初始化器,而且在裏面調用父類的指定初始化器
  • 在 Swift 中,子類若是沒有新建立一個指定初始化器,而且沒有重寫父類的指定初始化器,則會繼承父類的指定初始化器和便利初始化器
  • 在 Swift 中,子類若是新建立一個指定初始化器,或者重寫了父類的某個指定初始化器,那麼就不會繼承父類的指定初始化器和便利初始化器;可是若是重寫了父類的全部指定初始化器,就會繼承父類的便利初始化器
  • 在 Swift 中,子類能夠把父類的指定初始化器重寫成便利初始化器
  • 在 Swift 中,若是子類沒有直接繼承父類的指定初始化器和便利初始化器,則必須實現父類中required修飾的初始化器

參考資料

Initialization

相關文章
相關標籤/搜索