swift4.03 学习笔记(14)

Initialization(初始化)

初始化是准备一个类,结构体或枚举的实例的过程。

为存储属性设置初始值

当类或结构体的实例被创建时,你必须为它们的属性初始化一个合适的值。

这些属性可以在初始化器中赋值,也可以在定义属性时赋一个默认值。

Initializers(初始化器)

使用init关键字定义初始化器

init() {
    // perform some initialization here
}

使用初始化器初始化一个结构体

struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// Prints "The default temperature is 32.0° Fahrenheit"

属性的默认值

如果每次初始化时,属性的值都一样,最好给这个属性设置默认值:

struct Fahrenheit {
    var temperature = 32.0
}

初始化参数

定义带参数的初始化器

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius is 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0

参数名和参数标签

同函数和方法一样,初始化器参数也可以提供在初始化器内部使用的参数名,和外部调用时使用的参数标签名。

标签名:没有特别指定时,参数名和标签名相同

struct Color {
    let red, green, blue: Double
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    init(white: Double) {
        red   = white
        green = white
        blue  = white
    }
}

调用初始化时必须使用标签名:

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)

不然,编译时就会报错:

let veryGreen = Color(0.0, 1.0, 0.0)
// this reports a compile-time error - argument labels are required

没有标签名的初始化器参数

与函数和方法类似,在初始化器的参数中,可以用“_”隐藏外部调用时需要的标签名

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
    init(_ celsius: Double) {
        temperatureInCelsius = celsius
    }
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius is 37.0

可选类型属性(Optional)

可选类型的属性,可以不用在初始化时设置值,因为可选类型的属性允许分配nil值

class SurveyQuestion {
    var text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// Prints "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."

初始化时给常量属性赋值

常量只能赋默认值或在初始化器中赋值,这与Java类似

class SurveyQuestion {
    let text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// Prints "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)"

默认初始化器

Swift自动的为类和结构体提供了一个默认的初始化器

默认为类提供了一个无参的初始化器

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

默认为结构体提供了一个按顺序包含所有参数的初始化器

struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

值类型的初始化器委托

在实例的初始化过程中,一个初始化器可以调用其他初始化器,这个过程叫初始化器委托。这可以避免多个初始化器中出现重复的代码。

值类型和引用类型的委托规则是不一样的。值类型不支持继承(结构体,枚举),所以初始化器委托只能调用自身其他的初始化器。而类支持继承,因此类还有确保所有继承的属性被赋值了一个合理的值。

结构体初始化器委托

struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}

struct Rect {
    var origin = Point()
    var size = Size()
    init() {}
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}

类的继承和初始化

类的所有属性(包括从父类继承的属性)在初始化期间都有被赋值一个合适的值。

为此Swift提供了designated初始化器和convenience初始化器,方便类初始化

designated初始化器和convenience初始化器

designated初始化器是类的初始初始器,它必须初始化本类定义的所有需要初始化的属性,调用父类合适的初始化器,完成类的向上初始化执行链条。

每一个类必须至少有一个designated初始化器。

convenience初始化器,是一个间接的初始化器。你可以定义convenience初始化器调用designated 初始化器,也可以直接使用convenience初始化器实例化一个类。

convenience初始化器在类中需要的时候才定义。

designated初始化器和convenience初始化器语法

designated初始化器语法

init(parameters) {
    statements
}

convenience初始化器语法

convenience init(parameters) {
    statements
}

引用类型的初始化器委托

为了简化 designated 和 convenience 初始化器的关系,Swift应用三种简单的规则:

  • 规则1,designated 初始化器,必须调用一个直接父类的designated初始化器。
  • 规则2,convenience初始化器, 必须调用同类的designated初始化器。
  • 规则3,convenience初始化器,最终必须调用一个designated初始化器。

简单的来讲:

  • designated 初始化器向上委托
  • convenience初始化器横向委托

如图:


swift4.03 学习笔记(14)_第1张图片
image

在父类中定义了一个designated 初始化器和两个convenience初始化器,一个convenience初始化器调用了另一个convenience初始化器,然后这个convenience初始化器调用了designated 初始化器。

在子类中,定义了两个designated 初始化器和一个convenience初始化器,一个convenience初始化器调用了一个designated 初始化器,然后这个designated 初始化器调用了父类的designated 初始化器。另一个designated 初始化器调用父类的designated初始化器。

下图是一个更复杂的类的层级调用图:


swift4.03 学习笔记(14)_第2张图片
image

两阶段初始化

第一阶段,给每一个属性赋初始值。

第二阶段,给每一个实例在被认为可以使用之前,定制属性的机会。

Swift编译器执行四种有用的安全检查,确保两个阶段的初始化正确完成。

  • Safety check 1 , designated 初始化器必须在调用父类的designated 初始化器之前,所有本类的属性被初始化了。
  • Safety check 2,designated 初始化器必须在给父类属性赋值之前调用父类的designated 初始化器,赋值调用父类的designated 初始化器时会覆盖掉子类给父类属性赋的值。
  • Safety check 3,convenience 初始化器必须在给任何属性赋值之前调用其他初始化器,否则会被本地的designated 初始化器覆盖掉赋的值。
  • Safety check 4,第一阶段的初始化完成之前,初始化器,不能调用任何实例方法,不能使用self相关的表达式。

基于上面提到的四个安全检查,两阶段初始化的运行过程:

第一阶段:

  • 调用designated 或 convenience初始化器
  • 新实例的内存被分配,但还没有初始化
  • 类的designated 初始化器确认了当前类的所有属性有值,属性的内存还没有被初始化。
  • designated 初始化调用父类初始器,父类的初始化器对它自己的属性做相同的事情。
  • 继续向上调用直到最顶层父类。
  • 到达最顶层父类后,所有的属性已经都被确认有值了,然后实例的内存被初始化,第一阶段完成。

第二阶段

  • 按照调用链,从顶层父类回到当前类,在链中的每一个designated 初始化器可以有选择的更深入的定制实例,现在在初始化器中可以访问self,可以修改自己的属性,可以访问实例方法,等。
  • 最后,链中的任何convenience 初始化器有选择的定制实例,使用self。

初始化器的继承和重写

在Swift中,子类不会默认继承父类的初始化器。

如果你在子类实现的初始化器与父类的初始化器匹配,那么你已经重写了父类的初始化器,你就需要使用override修饰符。

和重写属性,方法,脚本一样,override修饰符,让Swift检查父类是否有被重写的初始化器.

注意

即使是convenience初始化器与父类的designated初始化器匹配也要写override修饰符

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
    
    init(numberOfWheels:Int){
        self.numberOfWheels = numberOfWheels
    }
        
}

class Bicycle: Vehicle {
    init(num:Int){
         super.init(numberOfWheels:num)
    }
    override convenience init(numberOfWheels:Int) {
        self.init(num:numberOfWheels)
       
    }
}

相反地,如果子类的designated初始化器与父类的convenience初始化器匹配,根据引用类型初始化器的委托规则,父类的convenience初始化器永远不可能被调到,因此不用加override修饰符。

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
    
    convenience init(numberOfWheels:Int) {
        self.init()
         self.numberOfWheels = numberOfWheels
    }
        
}

class Bicycle: Vehicle {
    init(numberOfWheels:Int){
         super.init()
         self.numberOfWheels = numberOfWheels
    }
   
}

let b = Bicycle(numberOfWheels:2)
print(b.description)

初始化器的自动继承

前面提到,子类不会默认的继承父类的初始化器,但是在一些条件下,子类会默认继承父类的初始化器。

假定,子类的所有属性都有默认值,那么子类的自动继承遵循两个规则:

  • 规则1,如果子类没有定义任何designated 初始化器,子类默认继承父类所有的designated 初始化器
  • 如果子类定义了一个designated初始化器,或者是继承父类的designated初始化器,子类自动继承所有父类的convenience 初始化器。

Designated and Convenience Initializers in Action

定义Food类

class Food {
    var name: String
    init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

调用designated 初始化器,完成实例的初始化。

let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"

调用convenience 初始化器,完成实例的初始化。

let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"

调用过程如图:


swift4.03 学习笔记(14)_第3张图片
image

定义RecipeIngredient类,并继承Food

class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}

按照自动继承规则它继承了父类Food的convenience初始化器init().

这三种方式都可以初始化RecipeIngredient。

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

调用过程:


swift4.03 学习笔记(14)_第4张图片
image

定义ShoppingListItem类,并继承RecipeIngredient

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ✔" : " ✘"
        return output
    }
}

按照初始化器的自动继承规则,它继承了父类RecipeIngredient所有的初始化器:两个convenience 初始化器init(),init(name:) ,一个designated初始化器init(name:,quantity:)

如图:


swift4.03 学习笔记(14)_第5张图片
image

可失败的初始化器

使用"?",定义一个可失败的初始化器:

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

使用可失败的初始化器初始化的实例是optional 类型的,可以为nil

let someCreature = Animal(species: "Giraffe")
// someCreature is of type Animal?, not Animal
 
if let giraffe = someCreature {
    print("An animal was initialized with a species of \(giraffe.species)")
}
// Prints "An animal was initialized with a species of Giraffe"
枚举类型中定义可失败的初始化器
enum TemperatureUnit {
    case kelvin, celsius, fahrenheit
    init?(symbol: Character) {
        switch symbol {
        case "K":
            self = .kelvin
        case "C":
            self = .celsius
        case "F":
            self = .fahrenheit
        default:
            return nil
        }
    }
}

使用可失败初始化器对TemperatureUnit初始化

let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "This is a defined temperature unit, so initialization succeeded."
 
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
    print("This is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

枚举使用行值定义的可失败初始化器

枚举自动生成以行值为参数的可失败初始化器。

enum TemperatureUnit: Character {
    case kelvin = "K", celsius = "C", fahrenheit = "F"
}
 
let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "This is a defined temperature unit, so initialization succeeded."
 
let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
    print("This is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

没有在枚举TemperatureUnit定义初始化器TemperatureUnit(rawValue:),但是可以使用TemperatureUnit(rawValue:)对枚举的实例初始化。

初始化失败的传播

类,结构体,枚举的可失败初始化器可以调用同类型中其他的可失败初始化器。

子类也可以调用父类的可失败初始化器。

class Product {
    let name: String
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}
 
class CartItem: Product {
    let quantity: Int
    init?(name: String, quantity: Int) {
        if quantity < 1 { return nil }
        self.quantity = quantity
        super.init(name: name)
    }
}

可失败初始化器调用链中有一个失败就会理解返回nil

if let oneUnnamed = CartItem(name: "", quantity: 1) {
    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
    print("Unable to initialize one unnamed product")
}
// Prints "Unable to initialize one unnamed product"

覆盖可失败的初始化器

在子类中,可以像覆盖其他初始化器那样覆盖可失败初始化器。

也可以使用不可失败初始化器覆盖父类的可失败初始化器。

父类Document定义了一个可失败的初始化器 init?(name:)

class Document {
    var name: String?
    // this initializer creates a document with a nil name value
    init() {}
    // this initializer creates a document with a nonempty name value
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

子类AutomaticallyNamedDocument用不可失败的初始化器 init(name:)覆盖了父类的可失败初始化器 init?(name:)

class AutomaticallyNamedDocument: Document {
    override init() {
        super.init()
        self.name = "[Untitled]"
    }
    override init(name: String) {
        super.init()
        if name.isEmpty {
            self.name = "[Untitled]"
        } else {
            self.name = name
        }
    }
}

你可以在子类的不可失败初始化器中调用父类的可失败初始化器,如果父类的可失败初始化器返回nil,程序会报运行时错误。

class UntitledDocument: Document {
    override init() {
        super.init(name: "[Untitled]")!
    }
}
可失败的初始化器init!

init!生成隐式可选类型的实例。

  • 可以在init!初始化器中调用init?。
  • 可以在init?中调用init!.
  • 可以在init中调用init!,如果init!返回nil,会报运行时错误。

Required Initializers

使用required 修饰符定义的初始化器,在每一个子类中必须实现这个初始化器。

在父类中定义了required初始化器。

class SomeClass {
    required init() {
        // initializer implementation goes here
    }
}

在子类中覆盖了父类的required初始化器。覆盖required初始化器不需要写override修饰符。

class SomeSubclass: SomeClass {
    required init() {
        // subclass implementation of the required initializer goes here
    }
}

用闭包或者函数设置属性的默认值

使用全局函数设置默认值。

func someMethod()->String{
    return "hello,my world"
}

class SomeClass{
    let h=someMethod()
}

var t = SomeClass()

print(t.h)
//hello,my world

使用闭包设置默认值

class SomeClass {
    let someProperty: SomeType = {
        // create a default value for someProperty inside this closure
        // someValue must be of the same type as SomeType
        return someValue
    }()
}

在闭包后面加括号,告诉Swift要执行闭包。如果没有加括号,是把闭包赋值给了属性而不是把闭包的返回值给了属性。

注意

在使用闭包时其他属性还没有被初始化,不能使用其他属性,即使属性有默认值也不能使用,不能使用self。

你可能感兴趣的:(swift4.03 学习笔记(14))