Swift 关于初始化方法的 QA

Q: swift 和 OC 初始化(init)方法的区别? Swift 的初始化方法和普通的 swift 方法有什么区别的点吗?

init() {
    desktop = "桌子"
}

A: 和 OC 的初始化方法相比 1 - 初始化顺序的区别, swift 先初始化自己的成员变量,然后在调用父类的初始化方法,OC 的调用则相反 2 - swift 不需要 return 方法,写就行了 3 - swift 子类默认不继承父类的初始化方法,但是遇到一些 case 是自动继承的。 参考这里 Automatic Initializer Inheritance 和普通的 swift 方法相比 1 - 没有 func 关键字,只有 init 关键字 Note - 1 - 但是和普通的 swift 方法一样,后面的参数的语法是一样的,即下面这种初始化方法完全没有问题.

class House {
    let desktop:String    #Note-这儿是 let 照样可以在初始化方法中赋值!
    init(with desktop:String) {
        self.desktop = desktop
    }
}

Q: swift 中什么是初始化委托(Initializer delegation),有啥用?? A: 就是在一个初始化方法中调用另外一个初始化方法,作用是可以减少重复的初始化代码。 这个难点在于,类的是支持继承的(结构体/枚举不支持继承相对简单,直接本类提供对应方法即可),需要保证存储属性的初始化一致。 举个官方的例子 -

struct Rect {
    var origin = Point()
    var size = Size()
    init() {}
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
}
extension Rect {         //初始化方法放在 Extension 更合适.
    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 - Initializer Delegation for Value Types (opens new window) #Swift 官方的说明 2 - Extensions -> Initializers (opens new window) #Swift 官方的说明

Q: 下面这种写法有问题吗?会编译报错吗?

class People {
    var mount:Int = 0
}
class ChinesePeople: People {
    var name:String
    init(name:String) {
        self.name = name  //这儿没有调用父类的 super.init()
    }
}

A: 没问题,不会编译报错。这儿其实是在 self.name = name 后面隐式调用了 super.init() 方法.. 能这么做的原因是所有的 stored property 都已经被初始化了.. Note 1 - 很多时候衡量初始化方法合理不合理的一个重要指标就是「是否所有的存储属性在初始化完毕之后都被赋值了」.. 上面这个例子之所以没有报错也是因为 mount 已经赋值了.

Q: 下面这种 case 执行的时候会执行父类的 CustomTT… ?

let ttc = TTClass()
class CustomTT {
    init() {
        print("CustomTT”)
    }
}
class TTClass: CustomTT {
    override init() {
        print("TTClass”)
    }
}

A: 会.. 理论上复写的话只会走子类的方法,但是初始化方法比较特殊,它会隐式的调用父类的初始化方法来完成初始话过程.

Instead of making an explicit call to super.init(), this initializer relies on an implicit call to its superclass’s initializer to complete the process.

参考地址: 1 - Swift - Initialization (opens new window)

Q: Swift 初始化方法 designated Initializers 和 convenience Initializers 是干嘛的有啥区别? A: designated initializers 是一个类的主要的初始化方法。它做了点啥呢?主要就是初始化当前类的所有存储属性,以及通过调用父类的合适的初始化方法来继续完成初始化。一般来说一个类必须要有一个 convenience initializers 相对来说并不是主要的初始化方法,所以从字面意思来说是「便捷的初始化方法」,这个便捷初始化方法类可以没有。 Swift 官方给出的 convenience/designated initializers 的三个法则 - 1 - designated initializer 必须调用父类相关的 designated initializer,举下面这个例子-

class People {
    var mount:Int
    init(with mount:Int) {
        self.mount = mount
    }
}
class ChinesePeople: People {
    var country:String
    override init(with mount: Int) {
        country = “china"
        //Note
        //1 - 这儿必须调用同样的 designated initializer 方法.
        //2 - 如果不调用会报编译错误: 'super.init' isn't called on all paths before returning from initializer
        //3 - 如果调用 super.init() 报编译错误:  Missing argument for parameter 'with' in call
        super.init(with: mount)
    }
}

2 - convenience initializer 必须调用类中另外一个初始化方法.

let chinesePeople = ChinesePeople.init(country: “Chinese”)  #使用 convenience 进行初始化
class People {
    var mount:Int
    init(with mount:Int) {
        self.mount = mount
    }
}

class ChinesePeople: People {
    var country:String
    init(with mount: Int,country:String) {
        self.country = country
        super.init(with: mount)
    }
    convenience init(country:String) {
        //Note
        //1 - 必须要调用另外一个初始化方法.
        //2 - 不能直接调用调用父类的的初始化方法,否则会报编译错误:Convenience initializer for 'ChinesePeople' must delegate (with 'self.init') rather than chaining to a superclass initializer (with 'super.init')
        self.init(with:14亿人, country:country)
    }
}

3 - convenience initializer 必须最终调用一个 designated initializer 方法

Note 1 - Swift 类的初始化方法复杂在哪,我个人感觉就是在于引入复杂的继承体系后,初始化变得非常复杂。 2 - 通常来说大部分的类都只有一个 designated initializers,这很好理解,因为太多的 designated initializers 会让调用者感到迷惑,我究竟应该用哪个,而且也不利于开发者自己维护。 3 - convenience initializer 一般都是只能在同一个类层级中进行调用,即不能直接调用父类的初始化方法。而 designated initializers 是只能调用父类的。可以参考下面这张图...

Jietu20200103-190332

参考地址: 1 - Swift - Designated Initializers and Convenience Initializers (opens new window)

Q: 默认初始化方法和 designated initializer 是啥关系? A: 通常对于一个类来说,默认初始化方法就是 designated initializer

“The default initializer (when available) is always a designated initializer for a class”

Q: 还是关于 designated initializer 的问题,考虑下面这种 case 到底哪个是 designated initializer?

class People {
    var mount:Int
    init() {
        mount = 0
    }
    init(with mount:Int) {
        self.mount = mount
    }
}

A: 首先考虑一下 designated initializer 定义吧.. “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 initializer,但这两种都算是 designated initializer。 Note 1 - 其实理论上上面这两种的实现基本上没有啥差别,只不过一个是使用默认值,一个使用外部参数。

Q: Two-Phase Initialization 是啥意思?有啥用? A: Two-Phase Initialization 中文翻译过来就是「两阶段初始化」,两阶段都做啥了? 第一阶段:所有存储属性都被赋初始值。 第二阶段:当前类有机会在实例使用前对存储属性进行自定制。

class Vehicle {
    var numberOfWheels = 0
}
class Bicycle: Vehicle {
    override init() {
        super.init()            //这是第一阶段
        numberOfWheels = 2      //这是第二阶段
    }
}

Note 1 - 两阶段初始化其实并不是什么新技术,而是 Swift 官方的一个提示「告诉你在继承体系中如何正确的初始化存储属性的方式」

Q: 我太难了,所以作为子类(subclass)怎么正确实现自己的初始方法呢? A: 我觉得应该分为三步 1 - 初始化自己的属性 2 - 调用父类的 designated initializer 3 - 修改父类的属性 举个例子 -

class Vehicle {
    var numberOfWheels = 0
}
class Bicycle: Vehicle {
    var isMobike:Bool
    override init() {
        isMobike = false        //1 - 初始化子类自己的独有属性
        super.init()            //2 - 调用父类的 designated initializer
        numberOfWheels = 2      //3 - 修改父类的属性
    }
}

Q: 有的 init 方法之前要加 requried 是为啥?? A: 一个类的初始化方法前面加 requried 那要求继承当前类的所有子类都要加上 requried. Note 1 - 如果当前类已经用 requried 标记初始化方法的话,子类再复写的话就不用加 override 关键字了..

参考地址: 1 - Initialization - Required Initializers (opens new window) #Swift 官方的说明

Q: ViewController 的初始化方法应该怎么写?为啥下面这种方式会报错?

class TTViewController: UIViewController {
    init() {
        super.init()  #CompileError:Must call a designated initializer of the superclass 'UIViewController'
    }
}

A: 我觉得首先看下面这种 case 自定制视图控制器(从代码模板生成,别的啥都不动)调用 init() 方法就不行.为啥呢?

UIViewController.init()  #CompileOK
TTViewController.init()  #CompileError Missing argument for parameter 'coder' in call

因为 swift 里默认是不继承初始化方法的,只是在一些 case 里才会继承。Swift 也没有提供默认的初始化方法。 那么就引入了当前问题,如何自定制初始化方法呢? 继承 UIViewController 的 TTViewController 默认没有 init 方法,所以我们可以自定制 Init 方法 方法一 直接实现 init() 方法并在里面调用 init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) 方法,API 说明里表示这个方法是 designated initializer 方法

init() {
    super.init(nibName: nil, bundle: nil)
}

方法二 #网上提供的一种方法,但是亲测会报错

convenience init() {
    self.init(nibName:nil, bundle:nil) #CompilerError:Ambiguous reference to member 'init()'
}

然后子类就可以愉快的通过 TTViewController.init() 这种方式初始化视图控制器了… 要不然还得 TTViewController.init(nib:nil, bundle:nil)...

Note 1 - init 方法在 UIViewController 的子类里属于自定制方法,并不属于默认方法.

参考地址: 1 - Fatal error: use of unimplemented initializer 'init(coder:)' for class (opens new window) 2 - Why can't I call the default super.init() on UIViewController in Swift? (opens new window) 3 - How does UIViewController manage to have a default no-args initializer in Swift? (opens new window)

Q: initWithCoder 是啥初始化方法? 为啥自定制 UIViewController init 方法必须实现这个方法,不实现就报错.. A: 自定制 UIViewController 初始化方法必须调用它提供的 designated initializer -> init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) 而这个方法本质是通过 coder 实现的,所以必须实现 initWithCoder 方法。尽管我们自定制以及使用 UIViewController 的时候是从代码直接初始化的,并没有用到 nib 文件,但是为了保证实现的完整性,需要默认实现这个方法

When instantiating a view controller from a storyboard, iOS initializes the new view controller by calling its init(coder:) method instead of this method and sets the nibName property to a nib file stored inside the storyboard.

系统提供的默认试下如下,大概意思是如果你有一天通过 nib 初始化了,就必须要在这个里面做点事儿,否则就崩给你看。

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

参考地址: 1 - UIViewController and required init?(coder: ) (opens new window)