大刘的 iOS 自学笔记

Swift 初始化方法

2022-06-29  本文已影响0人  大刘

参考:

Swift对象的初始化类似于C++或Java的构造函数, 即Swift的构造函数就是init方法
Swift的初始化器没有返回值, 初始化器的主要任务是保证新实例在第一次使用之前能被正确地初始化

C++ Swift
构造方法 init 用于对象初始化
析构方法 deinit 用于清理资源

Swift强制要求成员初始化

Swift要求在初始化方法中所有的成员变量都已被正确赋值, 比如:

class Person {
    var name: String
}

这个类是无编译通过的, 因此, 可以有三种解决方式:

  1. 声明name时改为?可空类型(即可选类型)
  2. 声明时赋值: var name: String = ""
  3. 提供初如化方法, 在初始化方法中给name赋值
class Person {
    var name: String
}

init() {
    self.name = "haha"
}

默认初始化器

Swift为结构体和类提供默认的初始化器,前提是结构体和类要满足:

  1. 存储属性都有默认值
  2. 没有自定义初始化器
class Person {
    var name: String?
    var age = 1
    var sex = 1
}
var person = Person()

逐一成员初始化器

逐一成员初始化器只针对结构体有效

struct Student {
    var age: Int = 18
    var height: Float
    var name: String
    
    // public init() {} // 逐一初始化器是系统提供的, 如果自己实现了初始化器, 则系统不再提供逐一初始化器
}

let s1 = Student(age: 18, height: 180.0, name: "daliu")
let s2 = Student(height: 180.0, name: "daliu") // age在声明时已赋过值

值类型的初始化器委托

初始化器能够调用其他的初始化器,来实现实例的部分初始化,这样称之为初始化器委托(Initializer delegation)这样就可以避免在多个初始化器中存在重复的代码了, 初始化器委托的工作规则,以及允许的委托形式,值类型和类类型存在些不同的地方。对于值类型(结构体和枚举类型)来说,因为不支持继承,所以它们的初始化器委托的过程相对简单一些,因为他们只能委托他们自己的已经实现的初始化器。对于类来说,因为可能继承了父类的属性,所以初始化的过程需要保证所有的属性都有合适的初始值

对于值类型,你可以在自定义的初始化器中使用self.init来引用其他的初始化器,self.init也只能在在初始化器中使用
既然自定义了初始化器,那么默认的初始化器就会失效,包括逐一成员初始化器(对于值类型),如果你还想使用默认初始化器的话,就只能自己实现。因为这样可以保证这些初始化工作在自定义的的初始化器中完成,默认的初始化器或许不会帮你完成所有的初始化工作

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的强制要求, 类的所有存储属性,包括从父类继承的属性,在初始化过程中都必须赋值, 而且初始化方法之间可以相互调用, 因此Swift的类如果存在继承, 其初始化的流程稍微复杂. 这就引出几个概念:
指定初始化器(Designated Initializer)和便利初始化器(Convenience Initializer)

指定初始化器是一个类最基本的初始化器。指定初始化器完全初始化所有的属性,以及调用父类的初始化器来完成父类链的初始化工作, 每一个类至少有一个指定初始化器, 而便利初始化器里一般调用指定该类的初始化器

class Student {
    var age: Int
    var height: Float
    var name: String
    
    public init() {
        // 指定初始化器完全初始化所有的属性, 以及调用父类的初始化器来完成父类链的初始化工作
        self.age = 20
        self.height = 180.0
        self.name = "daliu"
    }
    
    public convenience init(name: String) {
        self.init() // 便利初始化器一般会调用指定初始化器
        self.name = name // 这句代码要在self.init()之后
    }
}

初始化调用规则

Swift要求在初始化方法中所有的非可选成员变量必须完全被显式初始化, 而且相对于其他常见语言, Swift初始化的调用规则很繁琐, 目的是为了保证安全, 防止属性在初始化之前被访问

一个简单的口诀就是

init_1.png

再复杂一点:

init_2.png
class Student: NSObject {
    var age: Int
    var height: Float
    var name: String
    
    public override init() {
        self.age = 20
        self.height = 180.0
        self.name = "daliu"
        super.init() // 如果不写,系统会自动添加
    }
    
    public convenience init(name: String) {
        // super.init() // Error: Convenience initializer for 'Student' must delegate (with 'self.init') rather than chaining to a superclass initializer (with 'super.init')
        self.init()
        self.name = name
        // super.init() // Error Convenience initializer for 'Student' must delegate (with 'self.init') rather than chaining to a superclass initializer (with 'super.init')
        
        // 由于便利初始化器平行委托, 因此在便利初始化器中可以调用self.init(...),但不可调用super.init(...)
    }
}

两阶段初始化

Swift中类的初始化分为两个阶段。第一个阶段,为每一个存储属性分配初始值。一旦每个存储属性的初始状态确定之后,第二阶段开始。在新的实例被使用之前,每个类都有机会来进一步地自定义它的存储属性
在所有的存储属性未被正确赋值前, self不可用, self的方法也不可调用

Swift编译器的安全检查

  1. 指定初始化器必须确保在向上委托父类初始化器之前,自己的所有存储属性都被初始化
    一旦一个对象的所有存储属性的初始状态确定下来之后,这个对象的内存就被认为是完全初始化了。所以,指定初始化器必须确保在处理父类链之前,它的所有存储属性是被初始化的. 因此在指定初始化器里, 必须先完成self所有存储属性的赋值, 才能调用super...
class Cat: NSObject {
    var name: String

    override init() {
        // super.init() // Error: Property 'self.name' not initialized at super.init call
        name = "cat"
        // super.init() // OK
    }
}

class Tiger : Cat {
    let power: Int

    override init() {
        // super.init() // 错误: 此时power还未正确初始化: Property 'self.power' not initialized at super.init call
        // 简记: 先确保自己, 再确保父类
        power = 10
        super.init() // OK: 在self(即Tiger)的所有属性完全初始化后,可以调用super
        name = "tiger"
    }
}
  1. 从父类继承过来的属性,必须在委托父类初始化器之后再为其赋值

即需要先调用 super..., 之后才能会super的属性赋值. 否则的话,为这些属性赋值之后,还是会被父类的初始化器所修改。

class Cat: NSObject {
    var name: String

    override init() {
        name = "cat"
    }
}

class Tiger : Cat {
    let power: Int

    override init() {
        power = 10
        // name = "tiger" // 错误: 要在super.init(...)之后才能为super中的属性赋值: 'self' used in property access 'name' before 'super.init' call
        super.init()
        name = "tiger" // OK, 在super.init后可以为super的属性赋值
        // 这样设计是可以理解的, 因为假设允许先为name赋值再调用super.init(), 则此处为name赋的值可能会被super.init()给覆盖掉
        // 因此为了防止这种覆盖, 总是先调self的初始化器, 之后才能为self的成员赋值, 同理总是先调用super的初始化器, 之后才能会super的成员赋值
    }
}
  1. 便利初始化器为任何属性赋值之前,必须调用其他初始化器
    同理, 这是因为否则的话,为这些属性赋值之后,还是可能会被其他初始化器所修改

  2. 在初始化过程第一阶段完成之前,初始化器不能调用任何实例方法,不能读取任何属性的值,以及不能引用self
    类的实例在第一阶段完成之前并非完全有效的。只有当第一阶段完成之后,属性才能被访问,方法才能被调用,类的实例才是有效的。

基于四个安全检查的两阶段初始化过程

技巧

虽然有其设计道理, 但Swift初始化还是比较繁琐, 在书写代码时可以简记为两点:

  1. 先自己后父类: 先确定自己的成员是OK的, 才能调用父类super
  2. 先初始化再定制: 为了防止覆盖, 在为self.属性赋值时必须先调用self.init(...), 同理在为super.属性赋值时必须先调用super.init(...)

示例:

self.init(...)
self.name = "xxx" // self init后定制属性
super.init(...)
super.age = 10 // super init后定制属性

designated、convenience初始化再次说明

在 Apple 的官方文档中讲到,Swift 定义了两种类初始化器类型,用来保证所有成员属性能够获得一个初始化值, 即 designated initializers 和 convenience initializers
Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.

designated initializers 是一个类的主初始化器(primary initializers),理论上来说是一个类初始化的必经之路(注:不同的初始化路径可能调用不同的 designated initializers)
fully initializes all properties:这点很明确,必须在 designated initializers 中完成所有成员属性的初始化
calls an appropriate superclass initializer:需要调用合适的父类初始化器完成初始化,不能随意调用

UIView的两个designated initializers:

public init(frame: CGRect)
public init?(coder aDecoder: NSCoder)

class CustomView: UIView {
    let param: Int

    // Designated initializer
    override init(frame: CGRect) {
        self.param = 1
        super.init(frame: frame)
    }

    // Required initializer
    // 'required' initializer 'init(coder:)' must be provided by subclass of 'UIView'
    // 也就是说, 如果父类的初始化器使用了required修饰, 则子类必须实现它
    required init?(coder aDecoder: NSCoder) {
        // fatalError("init(coder:) has not been implemented")
        self.param = 1
        super.init(coder: aDecoder)
    }

    // Convenience initializer
    convenience init(param: Int, frame: CGRect) {
        // self.param = param // 'let' property 'param' may not be initialized directly; use "self.init(...)" or "self = ..." instead
        self.init(frame: frame)
    }
}

可失败初始化器

可失败初始化器(Failable Initializers),即可以返回 nil 的初始化方法
就是将初始化返回值变成 optional value(在 init 后面加上 ?),并在不满足初始化条件的地方 return nil,这样,我们通过调用处判断是否有值即可知道是否初始化成功

class Product {
    let name: String
    init?(name: String) {
        if name.isEmpty {
            return nil
        }
        self.name = name
    }
}

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

枚举类型的可失败初始化器

在构造枚举类型实例的时候,可能根据输入的一个或者多个参数来选择合适的值。但是如果输入的参数和已有的值不匹配的话,则可以返回一个nil

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.")
}
// 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.

具有原始值的枚举类型的可失败初始化器

具有原始值的枚举类型本身就具有可失败的初始化器init?(rawValue:),如果输入的原始值匹配则输出值,如果输入的原始值不匹配的话就输出nil

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.

必需初始化器(Required Initializer)

在定义类的初始化器的时候用关键字required修饰的时候,说明每一个子类都必须实现这个初始化器

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

class SomeSubclass: SomeClass {
    required init() {
        // 在子类中实现的这个必修初始化器必须加上关键字required,但是不用加override关键字
        // subclass implementation of the required initializer goes here
    }
}
上一篇下一篇

猜你喜欢

热点阅读