Swift:构造过程

2018-07-17  本文已影响18人  伯wen

中文文档

一、存储属性的初始赋值

1、构造器
init() {
    // 在此处执行构造过程
}
struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// 打印 "The default temperature is 32.0° Fahrenheit"
2、默认属性值
struct Fahrenheit {
    var temperature = 32.0
}
struct Vehicle {
    let speed: Int
    var currentSpeed: Double
}
let vehicle = Vehicle(speed: 0, currentSpeed: 0)
init(speed: Int, currentSpeed: Double) {
    self.speed = speed
    self.currentSpeed = currentSpeed
}
class Vehicle {
    let speed: Int
    var currentSpeed: Double
}

二、自定义构造过程

你可以通过输入参数和可选类型的属性来自定义构造过程,也可以在构造过程中给常量属性赋初值。

1、构造参数
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 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0
2、参数名和参数标签
然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。
因此在调用构造器时,主要通过构造器中的参数名和类型来确定应该被调用的构造器。
正因为参数如此重要,如果你在定义构造器时没有提供参数标签,
Swift 会为构造器的每个参数自动生成一个参数标签。
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)
// 报编译时错误,需要外部名称
3、不带参数标签的构造器参数
struct Celsius {
    var temperatureInCelsius: Double
    init(_ celsius: Double){
        temperatureInCelsius = celsius
    }
}

let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 为 37.0
4、可选属性类型
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()
// 打印 "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."
5、构造过程中常量属性的赋值

注意
对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。

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()
// 打印 "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)"

注意
如果类的常量属性, 在定义时指定了初始值, 就不能在构造方法中再次修改常量的值
只有在定义时, 没有指定常量属性的值, 才可以在构造方法中指定值

三、默认构造器

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()
1、结构体的逐一成员构造器
struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

四、值类型的构造器代理

值类型不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。
类则不同,它可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。

注意
假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(extension)中,而不是写在值类型的原始定义中。

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)
    }
}

五、类的继承和构造过程

1、指定构造器和便利构造器
2、指定构造器和便利构造器的语法
init(parameters) {
    statements
}
convenience init(parameters) {
    statements
}

指定构造器便利构造器最大的区别:
指定构造器只能调用父类的指定构造器, 没有父类, 不需要调用任何构造器
遍历构造器只能调用自己的其他构造器, 可以是自己的指定构造器, 也可以是自己的遍历构造器, 遍历构造器最终必须调用自己的指定构造器

3、类的构造器代理规则
规则 1: 指定构造器必须调用其直接父类的的指定构造器。
规则 2: 便利构造器必须调用同类中定义的其它构造器。
规则 3: 便利构造器最后必须调用指定构造器。
指定构造器必须总是向上代理
便利构造器必须总是横向代理
4、两段式构造过程
第一个阶段,类中的每个存储型属性赋一个初始值。
当每个存储型属性的初始值被赋值后,第二阶段开始,
它给每个类一次机会,在新实例准备使用之前进一步定制它们的存储型属性。
安全检查 1: 
指定构造器必须保证它所在类的所有属性都必须先初始化完成,
之后才能将其它构造任务向上代理给父类中的构造器。

如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。
为了满足这一规则,指定构造器必须保证它所在类的属性在它往上代理之前先完成初始化。

安全检查 2
指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器,
如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。

安全检查 3
便利构造器必须为任意属性(包括同类中定义的)赋新值之前代理调用同一类中的其它构造器,
如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。

安全检查 4
构造器在第一阶段构造完成之前,不能调用任何实例方法,
不能读取任何实例属性的值,不能引用 self 作为一个值。
阶段 1:
1、 某个指定构造器或便利构造器被调用。
2、 完成新实例内存的分配,但此时内存还没有被初始化。
3、 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
4、 指定构造器将调用父类的构造器,完成父类属性的初始化。
5、 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部。
6、 当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,

这个实例的内存被认为已经完全初始化。此时阶段 1 完成。
阶段 2:
1、从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。
构造器此时可以访问 self、修改它的属性并调用实例方法等等。
2、最终,任意构造器链中的便利构造器可以有机会定制实例和使用 self。
5、构造器的继承和重写
class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// 打印 "Bicycle: 2 wheel(s)"

注意
子类可以在初始化时修改继承来的变量属性,但是不能修改继承来的常量属性。

6、构造器的自动继承
规则 1:
如果子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。

规则 2:
如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,
还是提供了自定义实现——它将自动继承父类所有的便利构造器。

注意
对于规则 2,子类可以将父类的指定构造器实现为便利构造器。

7、指定构造器和便利构造器实践
class Food {
    var name: String
    init(name: String) {
        self.name = name
    }

    convenience init() {
        self.init(name: "[Unnamed]")
    }
}
let namedMeat = Food(name: "Bacon")
// namedMeat 的名字是 "Bacon"
let mysteryMeat = Food()
// mysteryMeat 的名字是 [Unnamed]
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)
    }
}

RecipeIngredient 类拥有一个指定构造器 init(name: String, quantity: Int),它可以用来填充 RecipeIngredient 实例的所有属性值。这个构造器一开始先将传入的 quantity 参数赋值给 quantity 属性,这个属性也是唯一在 RecipeIngredient 中新引入的属性。随后,构造器向上代理到父类 Foodinit(name: String)。这个过程满足两段式构造过程中的安全检查 1。

RecipeIngredient 也定义了一个便利构造器 init(name: String),它只通过 name 来创建 RecipeIngredient的实例。这个便利构造器假设任意 RecipeIngredient 实例的 quantity1,所以不需要显式指明数量即可创建出实例。这个便利构造器的定义可以更加方便和快捷地创建实例,并且避免了创建多个 quantity1RecipeIngredient 实例时的代码重复。这个便利构造器只是简单地横向代理到类中的指定构造器,并为 quantity参数传递 1

注意,RecipeIngredient 的便利构造器 init(name: String) 使用了跟 Food 中指定构造器 init(name: String) 相同的参数。由于这个便利构造器重写了父类的指定构造器 init(name: String),因此必须在前面使用 override 修饰符

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ✔" : " ✘"
        return output
    }
}
var breakfastList = [
    ShoppingListItem(),
    ShoppingListItem(name: "Bacon"),
    ShoppingListItem(name: "Eggs", quantity: 6),
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
    print(item.description)
}
// 1 x orange juice ✔
// 1 x bacon ✘
// 6 x eggs ✘

六、可失败构造器

注意
可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同。

注意
严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此你只是用 return nil 表明可失败构造器构造失败,而不要用关键字 return 来表明构造成功。

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty {
            return nil
        }
        self.species = species
    }
}
let someCreature = Animal(species: "Giraffe")
// someCreature 的类型是 Animal? 而不是 Animal

if let giraffe = someCreature {
    print("An animal was initialized with a species of \(giraffe.species)")
}
// 打印 "An animal was initialized with a species of Giraffe"
let anonymousCreature = Animal(species: "")
// anonymousCreature 的类型是 Animal?, 而不是 Animal

if anonymousCreature == nil {
    print("The anonymous creature could not be initialized")
}
// 打印 "The anonymous creature could not be initialized"
1、枚举类型的可失败构造器
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
        }
    }
}
let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}
// 打印 "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.")
}
// 打印 "This is not a defined temperature unit, so initialization failed."
2、带原始值的枚举类型的可失败构造器
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.")
}
// 打印 "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.")
}
// 打印 "This is not a defined temperature unit, so initialization failed."

注意:
如果枚举没有初始值, 那么就不会自动生成init?(rawValue:)构造器

3、构造失败的传递

注意
可失败构造器也可以代理到其它的非可失败构造器。通过这种方式,你可以增加一个可能的失败状态到现有的构造过程中。

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)
    }
}
if let twoSocks = CartItem(name: "sock", quantity: 2) {
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// 打印 "Item: sock, quantity: 2"
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    print("Unable to initialize zero shirts")
}
// 打印 "Unable to initialize zero shirts"
if let oneUnnamed = CartItem(name: "", quantity: 1) {
    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
    print("Unable to initialize one unnamed product")
}
// 打印 "Unable to initialize one unnamed product"
4、重写一个可失败构造器

注意
你可以用非可失败构造器重写可失败构造器,但反过来却不行。

class Document {
    var name: String?
    // 该构造器创建了一个 name 属性的值为 nil 的 document 实例
    init() {}
    // 该构造器创建了一个 name 属性的值为非空字符串的 document 实例
    init?(name: String) {
        self.name = name
        if name.isEmpty { return nil }
    }
}
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
        }
    }
}
class UntitledDocument: Document {
    override init() {
        super.init(name: "[Untitled]")!
    }
}
5、init!可失败构造器

七、必要构造器

class SomeClass {
    required init() {
        // 构造器的实现代码
    }
}
class SomeSubclass: SomeClass {
    required init() {
        // 构造器的实现代码
    }
}

注意
如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现。

八、通过闭包或函数设置属性的默认值

class SomeClass {
    let someProperty: SomeType = {
        // 在这个闭包中给 someProperty 创建一个默认值
        // someValue 必须和 SomeType 类型相同
        return someValue
    }()
}

注意
如果你使用闭包来初始化属性,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能在闭包里访问其它属性,即使这些属性有默认值。同样,你也不能使用隐式的 self 属性,或者调用任何实例方法。

上一篇下一篇

猜你喜欢

热点阅读