关于ios:Swift-类构造器的使用

7次阅读

共计 8367 个字符,预计需要花费 21 分钟才能阅读完成。

程序员连本人写的源代码都不想读, 怎么可能看他人写的源代码! 每半年取得的常识相当于之前取得的全副常识的总和.

集体感觉这句话还是蛮有情理的. 反正对于我来说, 每过一段的工夫回过头来看本人写的代码都感觉有很大的重构空间, 很多中央写的不够 PERFECT, 尽管我不是一个 处女座, 然而对于代码的强壮和整洁还是很留神的.

接下来, 我来扯一扯谈一谈最近写 Swift 遇到的那些坑问题吧.

[](https://draveness.me/swift-zh…

首先说下 Swift 给我带来的感触吧, Swift 的刚开始应用的时候感觉还是太特么难用了能够的.

不过 Xcode 在 Swift 上的 补全极其慢 , 因为 Swift 所有的属性办法都是 默认公开 的, 所以可能是因为每次都要搜寻全局的符号导致主动补全十分迟缓, 重大影响了工作效率, 有同样的问题的请戳这里. 当然也不排除我电脑配置的影响, 不过重写的过程还是蛮顺利的, 没有遇到太多的问题, 而且应用了很多 Swift 的高级个性来缩减原来简短的 ObjC 代码.

[](https://draveness.me/swift-zh… init

好了而后, 谈一下我在这两天中写 Swift 时遇到的最大问题 —- 结构器 init 的应用.

注: 咱们在这篇博客中提到的结构器都为 类结构器, 在这里不提及值结构器的应用,详见文档.

刚刚应用这个结构器的时候我感觉到很困惑啊, 不就是个 init, 你给我搞这么多事件干什么? 我只想安安静静地初始化

[](https://draveness.me/swift-zh… init

当我听从以前写 ObjC 的习惯, 在 Swift 中键入 init 之后, 编译器揭示我:

复制代码



'required' initialize 'init(coder:)' must be provided by subclass of 'UITableViewCell'









这是什么意思 (,#゚Д゚), 好吧, 这个谬误居然能够点. 于是开心地双击, 而后呢, Xcode 在咱们的屏幕中主动生成了这些货色:

Swift

复制代码



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

}









随后, 我就如在 ObjC 中一样在 init 办法中调用了 super.init(), (/= _ =)/~┴┴ 怎么还有谬误?

这是啥意思?

复制代码



Must call a designated initializer of the superclass 'UITableViewCell'









必须调用一个 UITableViewCell 的指定结构器. 算了先不论了, 持续写好了. 于是又呈现呢了上面的提醒:

复制代码



Convenience initializer for 'TableViewCell' must delegate (with 'self.init') rather than chaining to a superclass initializer (with 'super.init')









既然说 convenience 结构器不能调用 super.init, 那么依照谬误提醒改成 self.init 应该就好了.

复制代码



Could not find member 'Default'









既然报了这个谬误, 那么如果加上 UITableViewCellStyle

复制代码



Could not fond an overload for 'init' that accepts the supplied arguments









找不到 init 办法接管所提供参数的重载.

最初一个常见的谬误大略是这样的

复制代码



Property 'self.label' not initialized at super.init call









Orz, 到这里我曾经放弃了本人通过尝试来解决这些问题了. 于是我求助于 Google, 最初怒看苹果的官网文档并找到了以上谬误的全副答案.

iOS 开发交换技术群:563513413,不论你是大牛还是小白都欢送入驻,分享 BAT, 阿里面试题、面试教训,探讨技术,大家一起交流学习成长!

[](https://draveness.me/swift-zh… init 办法的正确姿态

苹果的官网文档对于结构器的局部请戳这里

在 Swift 中, 类的初始化有两种形式, 别离是

  1. Designated Initializer
  2. Convenience Initializer

Designated Initializer 在本篇博客中译为指定结构器, 而 Convenience Initializer 译为便当结构器.

指定结构器在一个类中必须至多有一个, 而便当结构器的数量没有限度.

[](https://draveness.me/swift-zh… (Designated Initializer)

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.

指定结构器是类的次要结构器, 要在指定结构器中初始化所有的属性, 并且要在调用父类适合的指定结构器.

每个类应该只有大量的指定结构器, 大多数类只有 一个 指定结构器, 咱们应用 Swift 做 iOS 开发时就会用到很多 UIKit 框架类的指定结构器, 比如说:

Swift

复制代码



init()

init(frame: CGRect)

init(style: UITableViewCellStyle, reuseIdentifier: String?)









这些都是类指定结构器, 并且这些办法的后面是没有任何的关键字的 (包含 override).

当定义一个指定结构器的时候, 必须调用父类的某一个指定结构器:

Swift

复制代码



init(imageName: String, prompt: String = "") {super.init(style: .Default, reuseIdentifier: nil)

    ...

}









在这里咱们的指定结构器调用了父类的指定结构器 super.init(style: .Default, reuseIdentifier: nil).

[](https://draveness.me/swift-zh… (Convenience Initializer)

Convenience initializers are secondary, supporting initializers for a class. You can define a convenience initializer to call a designated initializer from the same class as the convenience initializer with some of the designated initializer’s parameters set to default values. You can also define a convenience initializer to create an instance of that class for a specific use case or input value type.

便当结构器是类的主要结构器, 你须要让便当结构器调用同一个类中的指定结构器, 并将这个指定结构器中的参数填上你想要的默认参数.

如果你的类不须要便当结构器的话, 那么你就不用定义便当结构器, 便当结构器后面必须加上 convenience 关键字.

在这里咱们就不举例了, 然而咱们要提一下便当结构器的语法:

Swift

复制代码



convenience init(parameters) {statements}









[](https://draveness.me/swift-zh… 规定

定义 init 办法必须遵循三条规定

  1. 指定结构器必须调用它间接父类的指定结构器办法.
  2. 便当结构器必须调用同一个类中定义的其它初始化办法.
  3. 便当结构器在最初必须调用一个指定结构器.

如下图所示:

在图中, 只有指定结构器才能够调用父类的指定结构器, 而便当结构器是不能够的, 这也遵循了咱们之前所说的三条规定.

只有 init 办法遵循这三个规定就不会有任何问题.

  • 不过为什么要遵循这三条规定呢?
  • init 的办法的调用机制是什么呢?

[](https://draveness.me/swift-zh… 机制

在 Swift 中一个实例的初始化是分为两个阶段的

  1. 第一阶段是实例的所有属性被初始化.
  2. 第二阶段是实例的所有属性能够再次的调整以备之后的应用.

而这与 ObjC 的区别次要在于第一局部, 因为在 ObjC 中所有的属性如果不赋值都会默认被初始化为 nil或者 0. 而在 Swift 中能够所有属性的值由开发者来指定.

Swift 的编译器会对初始化的办法进行平安地查看已保障实例的初始化能够被 平安正确 的执行:

  1. 指定结构器必须要确保所有被类中提到的属性在代理向上调用父类的指定结构器前被初始化, 之后能力将其它结构工作代理给父类中的结构器.
  2. 指定结构器必须先向上代理调用父类中的结构器, 而后能力为任意属性赋值.
  3. 便当结构器必须先代理调用同一个类中的其余结构器, 而后再为属性赋值.
  4. 结构器在第一阶段结构实现之前, 不能调用任何实例办法, 不能读取任何实例属性的值,self 不能被援用.

接下来咱们来阐明一下类结构的两个阶段:

[](https://draveness.me/swift-zh… 1

  • 某个指定结构器或便当结构器被调用.
  • 实现新的实例内存的调配, 但此时内存还没有被初始化.
  • 指定结构器确保其所在类引入的 所有存储型属性 都已赋值. 存储型属性所属的内存实现初始化.
  • 指定结构器将调用父类的结构器, 实现父类属性的初始化.
  • 这个调用父类结构器的过程 沿着结构器链始终往上执行, 直到达到结构器链的最顶部.
  • 当达到了结构器链最顶部, 且已 确保所有实例蕴含的存储型属性都曾经赋值 ,这个实例的内存被认为曾经齐全初始化。此时 阶段 1 实现.

  1. 子类的便当结构器首先会被调用, 这时便当结构器无奈批改子类的任何属性.
  2. 便当结构器会调用子类中的指定结构器, 指定结构器 (子类) 要确保所有的属性都已赋值, 实现所属内存的初始化,
  3. 接着会指定结构器 (子类) 会调用父类中的指定结构器, 实现父类属性所属内存的初始化, 直到达到结构器链的最顶部. 所有的属性以及内存被齐全初始化, 而后进入第 阶段 2.

[](https://draveness.me/swift-zh… 2

  • 从顶部结构器链始终向下, 每个结构器链中类的指定结构器都有机会进一步定制实例. 结构器此时能够拜访 self, 批改它的属性并调用实例办法等等。
  • 最终, 任意结构器链中的便当结构器能够有机会定制实例和应用 self

  1. 父类中的指定结构器定制实例的属性 (可能).
  2. 子类中的指定结构器定制实例的属性.
  3. 子类中的便当结构器定制实例的属性.

[](https://draveness.me/swift-zh… 的继承和重载

Unlike subclasses in Objective-C, Swift subclasses do not inherit their superclass initializers by default. Swift’s approach prevents a situation in which a simple initializer from a superclass is inherited by a more specialized subclass and is used to create a new instance of the subclass that is not fully or correctly initialized.

跟 ObjC 不同, Swift 中的 子类默认不会继承来自父类的所有结构器 . 这样能够 避免谬误的继承并应用父类的结构器生成谬误的实例(可能导致子类中的属性没有被赋值而正确初始化). 与办法不同的一点是, 在重载结构器的时候, 你不须要增加 override 关键字.

尽管子类不会默认继承来自父类的结构器, 然而咱们也能够通过别的办法来主动继承来自父类的结构器, 结构器的继承就遵循以下的规定:

  1. 如果子类没有定义任何的指定结构器, 那么会默认继承所有来自父类的指定结构器.
  2. 如果子类提供了所有父类指定结构器的实现, 不论是通过 规定 1 继承过去的, 还是通过自定义实现的, 它将主动继承所有父类的便当结构器.

[](https://draveness.me/swift-zh…

咱们到目前为止曾经根本介绍了所有的结构器应用的注意事项, 接下来咱们剖析一下最开始谬误的起因.

[](https://draveness.me/swift-zh… 1

第一个谬误是因为, 咱们一开始尽管没有为指定结构器提供实现, 不过, 因为重载了指定结构器, 所以来自父类的指定结构器并不会被继承.

如果子类没有定义任何的指定结构器, 那么会默认继承所有来自父类的指定结构器.

init(coder aDecoder: NSCoder) 办法是来自父类的指定结构器, 因为这个结构器是 required, 必须要实现. 然而因为咱们曾经重载了 init(), 定义了一个指定结构器, 所以这个办法不会被继承, 要手动覆写, 这就是第一个谬误的起因.

[](https://draveness.me/swift-zh… 2

Swift

复制代码



class TableViewCell: UITableViewCell {init() { }

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

    }

}









咱们曾经手动覆写了这个办法, 而后, 因为 init() 办法尽管被重载了, 然而并没有调用父类的指定结构器:

指定结构器必须调用它最近父类的指定结构器.

所以咱们让这个指定结构器调用 super.init(style: UITableViewCellStyle, reuseIdentifier: String?), 解决了这个问题.

Swift

复制代码



class TableViewCell: UITableViewCell {init() {super.init(style: .Default, reuseIdentifier: nil)

    }

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

    }

}









[](https://draveness.me/swift-zh… 3

Swift

复制代码



class TableViewCell: UITableViewCell {convenience init() {super.init(style: .Default, reuseIdentifier: nil)

    }

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

    }

}









谬误 3 跟后面的两个谬误没有间接的分割. 这里的结构器是一个便当结构器 (留神后面的 convenience 关键字), 而这里的谬误违反了这一条规定:

便当结构器必须调用同一个类中定义的其它结构器 (指定或便当).

所以咱们让这个便当结构器调用同一个类的 self.init(style: UITableViewCellStyle, reuseIdentifier: String?) 的指定结构器.

Swift

复制代码



class TableViewCell: UITableViewCell {convenience init() {self.init(style: .Default, reuseIdentifier: nil)

    }

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

    }

}









而这段代码目前还是有问题的, 而这就是 谬误 4 的代码.

[](https://draveness.me/swift-zh… 4

谬误 4 的次要起因就是重载了父类的 init(coder aDecoder: NSCoder) 指定结构器, 导致父类的指定结构器 init(style: .Default, reuseIdentifier: nil) 并没有被以后类 TableViewCell 继承, 所以以后类中是没有 init(style: .Default, reuseIdentifier: nil) 指定结构器.

如果子类没有定义任何的指定结构器, 那么会默认继承所有来自父类的指定结构器.

Swift

复制代码



class TableViewCell: UITableViewCell {convenience init() {self.init(style: .Default, reuseIdentifier: nil)

    }

}









只须要删掉这个 init(coder aDecoder: NSCoder) 办法就能够解决这个谬误了.

[](https://draveness.me/swift-zh… 5

Swift

复制代码



class TableViewCell: UITableViewCell {

    let label : UILabel

    init(imageName: String) {super.init(style: .Default, reuseIdentifier: nil)

    }

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

    }

}









谬误 5 的次要起因是违反了这一条规定, 它在调用 super.init(style: .Default, reuseIdentifier: nil) 之前并没有初始化本人的所有属性.

指定结构器必须要确保所有被类中提到的属性在代理向上调用父类的指定结构器前被初始化, 之后能力将其它结构工作代理给父类中的结构器.

因为 label 属性不是 optional 的, 所以这个属性就必须初始化.

Swift

复制代码



init(imageName: String) {self.label = UILabel()

    super.init(style: .Default, reuseIdentifier: nil)

}









这是第一个解决的方法, 不过我个别应用另一种, 在属性定义的时候就为他说初始化一个值.

Swift

复制代码



class TableViewCell: UITableViewCell {let label = UILabel()

    init(imageName: String) {super.init(style: .Default, reuseIdentifier: nil)

    }

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

    }

}









这些就是我在应用 swift 的结构其中遇到的全副谬误了.

[](https://draveness.me/swift-zh…

Swift 中结构器须要遵循的规定还是很多的, 总结一下, 有以下规定:

  • 调用相干

    • 指定结构器必须调用它间接父类的指定结构器办法.
    • 便当结构器必须调用同一个类中定义的其它初始化办法.
    • 便当结构器在最初必须调用一个指定结构器.
  • 属性相干

    • 指定结构器必须要确保所有被类中提到的属性在代理向上调用父类的指定结构器前被初始化, 之后能力将其它结构工作代理给父类中的结构器.
    • 指定结构器必须先向上代理调用父类中的结构器, 而后能力为任意属性赋值.
    • 便当结构器必须先代理调用同一个类中的其余结构器, 而后再为属性赋值.
    • 结构器在第一阶段结构实现之前, 不能调用任何实例办法, 不能读取任何实例属性的值,self 不能被援用.
  • 继承相干

    • 如果子类没有定义任何的指定结构器, 那么会默认继承所有来自父类的指定结构器.
    • 如果子类提供了所有父类指定结构器的实现, 不论是通过上一条规定继承过去的, 还是通过自定义实现的, 它将主动继承所有父类的便当结构器.

Swift 中的结构器 init 中坑还是很多的, 而目前我也终于把这个结构器这个坑填上了, 最终决定还是要从新 具体 看一遍 Swift 的官网文档, 而整篇博客和问题的解决都是基于官网文档的. 应用下来 Swift 比 Objective-C 语言应用起来的注意事项和坑更多, 也有很多的黑魔法, 期待着咱们去开发和摸索.

正文完
 0