构造器(中)

值类型的构造器代理

构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。

构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给本身提供的其它构造器。类则不同,它可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节类的继承和构造过程中介绍。

对于值类型,你可以使用self.init在自定义的构造器中引用其它的属于相同值类型的构造器。并且你只能在构造器内部调用self.init

如果你为某个值类型定义了一个定制的构造器,你将无法访问到默认构造器(如果是结构体,则无法访问逐一对象构造器)。这个限制可以防止你在为值类型定义了一个更复杂的,完成了重要准备构造器之后,别人还是错误的使用了那个自动生成的构造器。

注意:假如你想通过默认构造器、逐一对象构造器以及你自己定制的构造器为值类型创建实例,我们建议你将自己定制的构造器写到扩展(extension)中,而不是跟值类型定义混在一起。

struct Size {
var width = 0.0
var height = 0.0
} struct Point {
var x = 0.0
var y = 0.0
} struct Rect {
var size = Size()
var origin = Point() init() {} init(origin: Point, size: Size){
self.size = size
self.origin = origin
} init(center: Point, size: Size){
let x = center.x - size.width / 2
let y = center.y - size.height / 2
self.init(origin:Point(x: x, y: y), size:size)
} init(x: Double, y: Double, width: Double, height: Double){
self.init(origin: Point(x: x, y: y), size: Size(width: width, height: height))
}
} let basicRect = Rect()
let originRect = Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 100.0, height: 100.0))
let centerRect = Rect(center: Point(x: 50.0, y: 50.0), size: Size(width: 100.0, height: 100.0))
let customRect = Rect(x: 25.0, y: 25.0, width: 50.0, height: 50.0)
// 第三和第四个构造方式内部都是在构造内部代理给“init(origin: Point, size: Size)” 构造器来提供属性的值

指定构造器和便利构造器

类里面的所有存储型属性--包括所有继承自父类的属性--都必须在构造过程中设置初始值。

Swift 提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。

指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。

每一个类都必须拥有至少一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。

便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。

你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。

class SomeClass {
var name = ""
var discription: String? init(name: String) {
// 这是自定构造器的写法
self.name = name
} convenience init(discription: String) {
// 这是便利构造器的写法
self.init(name: discription)
self.discription = discription
}
}

类的构造器的代理规则

Swift以三条规则来限制构造器之间的代理调用:

  • 指定构造器必须调用其直接父类的的指定构造器。
  • 便利构造器必须调用同一类中定义的其它构造器。
  • 便利构造器必须最终以调用一个指定构造器结束。

两段式构造过程

Swift 中类的构造过程包含两个阶段。第一个阶段,每个存储型属性通过引入它们的类的构造器来设置初始值。当每一个存储型属性值被确定后,第二阶段开始,它给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。

两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问;也可以防止属性被另外一个构造器意外地赋予不同的值。

Swift还会执行4中安全检查,以确保两段式构造过程能够顺利完成

  1. 指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。

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

  2. 指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
  3. 便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
  4. 构造器在第一阶段构造完成之前,不能调用任何实例方法、不能读取任何实例属性的值,self的值不能被引用。

    类实例在第一阶段结束以前并不是完全有效,仅能访问属性和调用方法,一旦完成第一阶段,该实例才会声明为有效实例。

以下是两段式构造过程中基于上述安全检查的构造流程展示:

阶段1:

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

阶段2:

  • 从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。
  • 最终,任意构造器链中的便利构造器可以有机会定制实例和使用self

构造器的继承和重写

跟 Objective-C 中的子类不同,Swift 中的子类不会默认继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。

假如你希望自定义的子类中能实现一个或多个跟父类相同的构造器,也许是为了完成一些定制的构造过程,你可以在你定制的子类中提供和重写与父类相同的构造器。

当你写一个父类中带有指定构造器的子类构造器时,你需要重写这个指定的构造器。因此,你必须在定义子类构造器时带上override修饰符。即使你重写系统提供的默认构造器也需要带上override修饰符。

相反地,如果你写了一个和父类便利构造器相匹配的子类构造器,子类都不能直接调用父类的便利构造器,每个规则都在上文构造器链有所描述。

class Vehicle {
var numberOfWheels = 0
// 为存储属性设置了默认值,所以不必自定义构造器,Swift也会生成一个默认构造器"init()"
var discription: String {
return "\(numberOfWheels) wheel(s)"
}
} class Bicycle: Vehicle {
// 子类自定义了一个构造器,这个构造器与父类的默认构造器相匹配,所以要加上关键字 "override"
override init() {
super.init()
numberOfWheels = 2
}
}

自动构造器的继承

如上所述,子类不会默认继承父类的构造器。但是如果特定条件可以满足,父类构造器是可以被自动继承的。在实践中,这意味着对于许多常见场景你不必重写父类的构造器,并且在尽可能安全的情况下以最小的代价来继承父类的构造器。

假设要为子类中引入的任意新属性提供默认值,请遵守以下2个规则:

  1. 如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
  2. 如果子类提供了所有父类指定构造器的实现--不管是通过规则1继承过来的,还是通过自定义实现的--它将自动继承所有父类的便利构造器。

即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。

演练

class Food {
var name: String
// 指定构造器
init(name: String) {
self.name = name
} // 便利构造器
convenience init() {
self.init(name: "[没有商品名]")
}
} class RecipeIngredient: Food {
var quantity: Int // 指定构造器
init(name: String, quantity: Int) {
// 这里遵循Swift的安全检查
// 1、确保子类的存储属性有值
self.quantity = quantity
// 2、根据构造链向上调用父类的指定构造器,使父类的所有存储属性有值
super.init(name: name)
} // 这里将父类的指定构造器重写为子类的便利构造器
override convenience init(name: String){
self.init(name: name, quantity: 1)
} // 子类提供了父类所有的指定构造器,所以子类也默认继承了父类的所有便利构造器,这满足了规则2:(如果子类提供了所有父类指定构造器的实现--不管是通过规则1继承过来的,还是通过自定义实现的--它将自动继承所有父类的便利构造器。)
} class ShoppingListItem: RecipeIngredient {
var purchased = false // 是否购买
var discription: String { // 描述
var output = "\(quantity) x \(name.lowercaseString)"
output += purchased ? "√" : "×"
return output
}
// 注意:这个子类没有定义任何指定构造器,那么它将遵守规则1 自动继承所有父类的指定构造器
} let salt = RecipeIngredient(name: "盐", quantity: 2)
let sugar = RecipeIngredient()
print("sugar name \(sugar.name), quantity \(sugar.quantity)")
// 打印出:"sugar name [没有商品名], quantity 1\n"
// 为什么quantity属性会是1? 因为sugar初始化的时候调用的是继承来的便利构造器,在便利构造器中“(init())”父类设置它将构造过程代理给父类的指定构造器"init(name: String)",不过这个便利构造器被继承后不再调用父类的"init(name: String)",而是子类override的"(convenience init(name: String))" // 开始创建购物清单
var list = [ShoppingListItem(),
ShoppingListItem(name: "Bacon"),
ShoppingListItem(name: "Eggs", quantity: 5)]
// 可以看到 ShoppingListItem这个子类继承了所有父类的构造方法 list[1].purchased = true for item in list {
print(item.discription)
}
// 1 x [没有商品名]×
// 1 x bacon√
// 5 x eggs×

学习Swift -- 构造器(中)的更多相关文章

  1. 学习Swift -- 构造器(下)

    构造器(下) 可失败的构造器 如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器,是非常有必要的.这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某 ...

  2. 学习Swift -- 构造器(上)

    构造器(上) 构造过程是为了使用某个类.结构体或枚举类型的实例而进行的准备过程.这个过程包含了为实例中的每个存储型属性设置初始值和为其执行必要的准备和初始化任务. 构造过程是通过定义构造器(Initi ...

  3. Swift学习笔记 - OC中关于NSClassFromString获取不到Swift类的解决方案

    在OC和Swift混编的过程中发现在OC中通过NSClassFromString获取不到Swift中的类,调研了一下发现问题所在,下面是我的解决方案: 问题的发现过程 UIViewController ...

  4. ios -- 教你如何轻松学习Swift语法(三) 完结篇

    前言:swift语法基础篇(二)来了,想学习swift的朋友可以拿去参考哦,有兴趣可以相互探讨,共同学习哦.      一.自动引用计数   1.自动引用计数工作机制      1.1 swift和o ...

  5. ios -- 教你如何轻松学习Swift语法(二)

    前言:swift语法基础篇(二)来了,想学习swift的朋友可以拿去参考哦,有兴趣可以相互探讨,共同学习哦.      一.可选类型(重点内容)   1.什么是可选类型?        1.1在OC开 ...

  6. ios -- 教你如何轻松学习Swift语法(一)

    目前随着公司开发模式的变更,swift也显得越发重要,相对来说,swift语言更加简洁,严谨.但对于我来说,感觉swift细节的处理很繁琐,可能是还没适应的缘故吧.基本每写一句代码,都要对变量的数据类 ...

  7. 一步一步学习Swift之(一):关于swift与开发环境配置

    一.什么是Swift? 1.Swift 是一种新的编程语言,用于编写 iOS 和 OS X 应用. 2.Swift 结合了 C 和 Objective-C 的优点并且不受 C 兼容性的限制. 3.Sw ...

  8. Swift构造器重载

    与函数一样,方法也存在重载,其重载的方式与函数一致.那么作为构造器的特殊方法,是否也存在重载呢?答案是肯定的.一.构造器重载概念Swift中函数重载的条件也适用于构造器,条件如下:函数有相同的名字:参 ...

  9. 学习Swift -- 协议(上)

    协议(上) 协议是Swift非常重要的部分,协议规定了用来实现某一特定工作或者功能所必需的方法和属性.类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能.任意能够满足协议要求 ...

随机推荐

  1. [转载]大道至简!!!从SAP HANA作为SAP加速器的方式,看ERP on HANA的春天

    I AM A ABAPER! 科技的进步,一定会使一些东西变得越来越精简! 大道至简!!! 文章很好!!!!!!!!!!! -------------------------------------- ...

  2. jquerymobile知识点:动态Grid的绑定以及刷新

    下面jquerymobile是ajax动态绑定和刷新的例子.直接上图以及代码. 下面是实例代码: //初始绑定 function GetInitBind(PageIndex, PageSize, sq ...

  3. JavaScript中childNodes、children、nodeValue、nodeType、parentNode、nextSibling详细讲解

    其中属性.元素(标签).文本都属于节点 <title></title> <scripttype="text/javascript"> windo ...

  4. 服装销售系统数据库课程设计(MVC)

    <数据库课程设计> 名称:Jia服装销售网站 姓名:陈文哲 学号:…… 班级:11软件工程 指导老师:索剑 目录 目录 1 需求分析 3 一:销售部门机构情况 3 二:销售部门的业务活动情 ...

  5. Block使用变量,让你的程序看起来清晰!

    <span style="font-size:24px;">为什么要使用block变量呢? 由于当我们的程序比較繁杂的时候,我们在一个函数中要调用一个函数,还须要在外边 ...

  6. iOS swift使用xib绘制UIView

    目标:用xib绘制一个UIView,在某个ViewController中调用. 三个文件:ViewController.Swift    DemoView.swift     DemoView.xib ...

  7. TCP 连接的建立和终止

    三路握手 建立一个TCP连接时会发生下述情形. (1)服务器必须准备好接受外来的连接.这通常通过调用socket.bind和listen这3个函数来完成的,我们称之为被动打开. (2)客户通过调用co ...

  8. display_errors & error_reporting(php调试安全)

    这两个选项对应的值即都可以在php.ini文件中设置,也都可以在php代码中使用相应的函数来设置. 在php.ini文件中的设置如下:

  9. 【Android】数据库的简单应用——创建数据库

    SQLiteOpenHelper是一个抽象类,要使用它必须写一个类继承它.SQLiteOpenHelper有两个抽象方法onCreate()和onUpgrade(),我们要在类里面重写这两个方法来实现 ...

  10. StarUML启动时候出现"System Error. Code:1722. RPC服务器不可用."错误的解决办法

    StarUML是用得很顺手的UML工具,但是启动时候每次都会出现"System Error. Code:1722. RPC服务器不可用."错误. 一般来说这个应该是某个Window ...