[Swift设计模式] init模式

484 阅读7分钟
更多内容,欢迎关注公众号:Swift花园
喜欢文章?不如来点赞关注吧

 

什么是初始化?

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

这个过程是通过构造器完成的。构造器是一种特殊的函数,使用保留的关键字 init 来声明,因此你不需要用到关键字 func。通常,构造器也不返回任何值。

 

初始化属性

在创建实例的时候,类和结构体必须为它们的所有存储属性设置合适的初始值。

首先想象一个非常简单的结构体,它只有两个属性。

struct Point {
    let x: Int
    let y: Int
}

上面的规则说到我们必须初始化所有的存储属性,那就让我们来创建第一个 init 方法吧。

struct Point {
    let x: Int
    let y: Int
    
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}

看起来就像其他的 Swift 函数是不是?现在我们来创建第一个实例。

let p1 = Point(x: 1, y: 1)

注意,如果属性是隐式解包可选型属性,或者普通的可选型属性,并且它们同时是变量而非常量的话,你是可以不初始化它们的。

相同的逻辑也适用于类,你可以尝试直接把 struct 关键字改成 class。不过,结构体是值类型,类是引用类型。这一点区别在这种类型中分别为我们提供了一些独有的能力。

 

逐一成员构造器 (只有结构体有)

结构体有一个好处,如果你没有提供自己的构造器,编译器会为你自动生成一个逐一成员构造器。不过有几点需要注意:生成的构造器将包含除拥有默认值的常量属性之外的所有属性,包括可选型;它的可访问性是 internal,所以对其他模块不可见。

只要结构体的任何一个存储属性是 private 的,那么结构体的默认逐一成员构造器也将是 private 的。同样,如果结构体的任何一个存储属性是 file private 的,那么逐一成员构造器也将是 file private 的。其他情况,这个构造器的访问级别是 internal。
struct Point {
    let x: Int
    let y: Int
    var key: Int!
    let label: String? = "zero"
}
let p1 = Point(x: 0, y: 0, key: 0) // provided by the memberwise init 

 

失败构造器

有些情况下你发现事情不对,你不想因此创建出糟糕的或者不合理的对象。比如你想要滤掉一堆点中的原点。

struct Point {
    let x: Int
    let y: Int
    
    init?(x: Int, y: Int) { // ? 问号标记构造器可能失败
        if x == 0 && y == 0 {
            return nil
        }
        self.x = x
        self.y = y
    }
}
let p1 = Point(x: 0, y: 0) // nil
let p2 = Point(x: 1, y: 1) // valid point

枚举遵循 RawRepresentable 协议,它们可以通过 rawValue 来构造,这也是一个失败构造器模式的例子。

enum Color: String {
    case red
    case blue
    case yellow
}

let c1 = Color(rawValue: "orange") // nil, 没有这个case
let c2 = Color(rawValue: "red") // .red

你也可以用 init! 代替 init?,前者会创建目标实例的隐式解包可选型。值得留意的是,类也可以有失败构造器。

 

初始化类

在 Swift 语言中,类是一等公民。你甚至不需要引入 Foundation 框架就可以创建新类。下面是一个用类来表示的 Point 。

class Point {
    let x: Int
    let y: Int
    
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}
let p1 = Point(x: 1, y: 1)

这回我们需要自行提供 init 方法,因为类并没有逐一成员构造器。它们是引用类型,加上继承逻辑,为类自动生成逐一成员构造器这件事要复杂很多。

 

默认构造器

以 Swift 类来讲,如果你为包括可选型在内的所有存储属性提供了默认值,你会获得一个自动生成的 internal 访问级别的默认构造器。实践中看起来就像这样:

class Point {
    let x: Int = 1
    let y: Int = 1
}
let p1 = Point()

或者像这样:

class Point {
    let x: Int = 0
    let y: Int = 0
    var key: Int!
    let label: String? = "zero" 
}
let p1 = Point()

上面这第二段代码看起来就有点不对劲了。为什么一个点会需要 key 和 label 属性呢?最好是能有一个子对象容纳这些额外的属性。那就让我们用类继承来重构一下这段代码吧。

 

指定构造器

指定构造器是一个类的主构造器

用另一种定义,指定构造器是没有用 convenience 标记的构造器,一个类可以有多个指定构造器。让我们沿用 Point 类,这次它将作为 NamedPoint 这个类的父类。

class Point {
    let x: Int
    let y: Int
    
    init(x: Int, y: Int) { // 这是指定构造器
        self.x = x
        self.y = y
    }
}

class NamedPoint: Point {

    let label: String?
    
    init(x: Int, y: Int, label: String?) { // 指定构造器
        self.label = label

        super.init(x: x, y: y)
    }

    init(point: Point, label: String?) { // 也是指定构造器
        self.label = label
        super.init(x: point.x, y: point.y) // 向上代理
    }
}

let p1 = NamedPoint(x: 1, y: 1, label: "first")
let p2 = NamedPoint(point: Point(x: 1, y: 1), label: "second")

指定构造器必须调用它的直接父类的某个指定构造器,所以你需要向上代理构造链。但在这之前,根据初始化的第一条规则,我们必须先确保当前类的所有存储属性被初始化。这意味着 Swift 语言其实是有一个两段式构造过程。

两段式构造

  1. 类的每个存储属性都会由引入这个属性的继承层级赋予一个初始值。
  2. 类的每一个继承层级都被给予时机定制自己的存储属性。

基于这两条规则,首先我们必须初始化 label 属性,然后向上代理构造过程。完成这两步之后我们才能真正开始使用对象。

 

便利构造器

它们是为了构造过程更简单而设计的。

在前面的例子中,如果我们为 point 提供一个 x 和 y 相等的构造器,某些情况会很方便。

class Point {
    let x: Int
    let y: Int
    
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
    convenience init(z: Int) {
        self.init(x: z, y: z) // 其实是调用指定构造器
    }
}
let p1 = Point(z: 1)

便利构造器必须调用同一个类层级里的其他构造器,最终调用到指定构造器。

结构体也可以有“便利”构造器,但你不需要写出 "convenience" 关键字。实际上它们的 init 方法略有不同,因为你可以在一个 init 方法里调用其他的 init 方法,外部看起来跟类的那套机制很相似。

struct Point {
    let x: Int
    let y: Int
    
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
    
    init(z: Int) {
        self.init(x: z, y: z)
    }
}

var p1 = Point(z: 1)

  

必需构造器

如果你在一个类里面用 required 标记构造器。那你将不得不在类继承体系里每个层级都用上它。因为子类也必须实现这种构造器。

class Point {
    let x: Int
    let y: Int
    
    required init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}

class NamedPoint: Point {
    let label: String?
    
    required init(x: Int, y: Int) {
        self.label = nil
        
        super.init(x: x, y: y)
    }
}

let p1 = NamedPoint(x: 1, y: 1)

 

重写构造器

在 Swift 中,构造器默认是不被子类继承的。如果你想要为子类提供一个父类已经存在的构造器,你需要用到 override 关键字。

class Point {
    let x: Int
    let y: Int
    
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}

class NamedPoint: Point {
    let label: String?
    
    override init(x: Int, y: Int) {
        self.label = nil
        
        super.init(x: x, y: y)
    }
}

let p1 = NamedPoint(x: 1, y: 1)

构造器继承有两条规则:

如果你的子类没有定义任何指定构造器,它将自动继承其父类的所有指定构造器。
如果你的子类提供其父类的指定构造器的实现——无论是通过规则1继承得到,或者自定义实现的,那么它都会自动继承父类的所有便利构造器。

 

析构器

类的实例被销毁时会立刻执行析构器。

当你的类被终结时,如果你想做一些扫尾工作,析构器将是你的选择。绝大多数情况下,你不要关心内存管理,因为 ARC 会为完成这件事。

class Point {
    let x: Int
    let y: Int
    
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
    
    deinit {
        print("Point is clenaed up.")
    }
}

var p1: Point? = Point(x: 1, y: 1)
p1 = nil // 析构器将被调用

 

我的公众号

这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~