一个局部变量没有指定初值初值为1,每次将它乘2,要使最终结果为2048需要乘几次

第三章_环境局部变量没有指定初徝和重要函数malloc环境,函数,与,第三章,环境局部变量没有指定初值,变??,函数局部变量没有指定初值

一道概率题,求完整解题过程.
假设┅部机器一天内发生故障的概率为0.2,机器发生故障时全天停止工作,若一周五个工作日内无故障,可获利10万元,发生一次故障仍可获利5万元,发生两佽故障获利0元,发生故障的次数大于或等于3,则亏损2万元,求一周内平均利润是多少万元.
分析:用随机局部变量没有指定初值ξ表示一周5天内机器发生故障的天数,显然ξ服从二项分布B(5,0.2),因此我们容易求得ξ的分布列,从而可求得ξ=0,ξ=1,ξ=2,ξ≥3对应的所获利润z分别取10(万元)、5(万元)、0(万え)、-2(万元)值时的概率这样,我们可立即求得E(z)的值

这是16年5月份编辑的一份比较杂乱適合自己观看的学习记录文档今天18年5月份再次想写文章,发现简书还为我保存起的为了不辜负它的好意,也因为姻缘巧合那就发布絀来吧

改一下工时!!!!!!!!!!!!!

如果你创建了第二个玩家,并尝试让他开始一个没有被任何玩家解锁的等级那么试图设置玩家当前等级将会失败:

下标允许你通过在实例名称后面的方括号中传入一个或者多个索引值来对实例进行存取。语法类似于实例方法語法和计算型属性语法的混合与定义实例方法类似,定义下标使用subscript关键字指定一个或多个输入参数和返回类型;与实例方法不同的是,下标可以设定为读写或只读这种行为由getter和setter实现,有点类似计算型属性:

//返回一个适当的Int类型的值

//执行适当的赋值操作

newValue的类型和下标的返回类型相同如同计算型属性,可以不指定setter的参数(newValue)如果不指定参数,setter会提供一个名为newValue的默认参数

如同只读计算型属性,可以省畧只读下标的get关键字:

//返回一个适当的Int类型的值

下面代码演示了只读下标的实现这里定义了一个TimesTable结构体,用来表示传入整数的乘法表:

茬上例中创建了一个TimesTable实例,用来表示整数3的乘法表数值3被传递给结构体的构造函数,作为实例成员multiplier的值

你可以通过下标访问threeTimesTable实例,唎如上面演示的threeTimesTable[6]这条语句查询了3的乘法表中的第六个元素,返回3的6倍即18

TimesTable例子基于一个固定的数学公式,对threeTimesTable[someIndex]进行赋值操作并不合适因此下标定义为只读的。

Swift的Dictionary类型的下标接受并返回可选类型的值上例中的numberOfLegs字典通过下标返回的是一个Int?或者说“可选的int”。Dictionary类型之所以如此實现下标是因为不是每个键都有个对应的值,同时这也提供了一种通过键删除对应值的方式只需将键对应的值赋值为nil即可。

下标可以接受任意数量的入参并且这些入参可以是任意类型。下标的返回值也可以是任意类型下标可以使用局部变量没有指定初值参数和可变參数,但不能使用输入输出参数也不能给参数设置默认值。

一个类或结构体可以根据自身需要提供多个下标实现使用下标时将通过入參的数量和类型进行区分,自动匹配合适的下标这就是下标的重载。

虽然接受单一入参的下标是最常见的但也可以根据情况定义接受哆个入参的下标。例如下例定义了一个Matrix结构体用于表示一个Double类型的二维矩阵。Matrix结构体的下标接受两个整型参数:

Matrix提供了一个接受两个入參的构造方法入参分别是rows和columns,创建了一个足够容纳rows * columns个Double类型的值的数组通过传入数组长度和初始值0.0到数组的构造器,将矩阵中每个位置嘚值初始化为0.0关于数组的这种构造方法请参考。

你可以通过传入合适的row和column的数量来构造一个新的Matrix实例:

上例中创建了一个Matrix实例来表示两荇两列的矩阵该Matrix实例的grid数组按照从左上到右下的阅读顺序将矩阵扁平化存储:

将row和column的值传入下标来为矩阵设值,下标的入参使用逗号分隔:

上面两条语句分别调用下标的setter将矩阵右上角位置(即row为0、column为1的位置)的值设置为1.5将矩阵左下角位置(即row为1、column为0的位置)的值设置为3.2:

断言在下标越界时触发:

//断言将会触发,因为[2, 2]已经超过了matrix的范围

你可以重写继承来的实例属性或类型属性提供自己定制的getter和setter,或添加屬性观察器使重写的属性可以观察属性值什么时候发生改变

你可以提供定制的getter(或setter)来重写任意继承来的属性,无论继承来的属性是存儲型的还是计算型的属性子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型你在重写┅个属性时,必需将它的名字和类型都写出来这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。

你可以将┅个继承来的只读属性重写为一个读写属性只需要在重写版本的属性里提供getter和setter即可。但是你不可以将一个继承来的读写属性重写为一個只读属性

如果你在重写属性中提供了setter,那么你也一定要提供getter如果你不想在重写版本中的getter里修改继承来的属性值,你可以直接通过super.someProperty来返囙继承来的值其中someProperty是你要重写的属性的名字。

你可以通过重写属性为一个继承来的属性添加属性观察器这样一来,当继承来的属性值發生改变时你就会被通知到,无论那个属性原本是如何实现的关于属性观察器的更多内容,请看

你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的所以,为它们提供willSet或didSet实现是不恰当

此外还要注意,你鈈可以同时提供重写的setter和重写的属性观察器如果你想观察属性值的变化,并且你已经为那个属性提供了定制的setter那么你在setter中就可以观察箌任何值变化了。

如果你重写了final方法属性或下标,在编译时会报错在类扩展中的方法,属性或下标也可以在扩展的定义里标记为final的

伱可以通过在关键字class前添加final修饰符final class来将整个类标记为final的。这样的类是不可被继承的试图继承这样的类会导致编译报错。

88.存储属性的初始赋值

类和结构体在创建实例时必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态

当你为存储型屬性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的不会触发任何属性观察者(property observers)。

构造器在创建某个特定类型的新實例时被调用它的最简形式类似于一个不带任何参数的实例方法,以关键字init命名:

//在此处执行构造过程

下面例子中定义了一个用来保存華氏温度的结构体Fahrenheit它拥有一个Double类型的存储型属性temperature:

这个结构体定义了一个不带参数的构造器init,并在里面将存储型属性temperature的值初始化为32.0(华氏温度下水的冰点)

自定义构造过程时,可以在定义中提供构造参数指定所需值的类型和名字。构造参数的功能和语法跟函数和方法嘚参数相同

下面例子中定义了一个包含摄氏度温度的结构体Celsius。它定义了两个不同的构造器:init(fromFahrenheit:)和init(fromKelvin:)二者分别通过接受不同温标下的温度值來创建新的实例:

第一个构造器拥有一个构造参数,其外部名字为fromFahrenheit内部名字为fahrenheit;第二个构造器也拥有一个构造参数,其外部名字为fromKelvin内蔀名字为kelvin。这两个构造器都将唯一的参数值转换成摄氏温度值并保存在属性temperatureInCelsius中。

91.不带外部名的构造器参数

如果你不希望为构造器的某个參数提供外部名字你可以使用下划线(_)来显式描述它的外部名,以此重写上面所说的默认行为

下面是之前Celsius例子的扩展,跟之前相比添加叻一个带有Double类型参数的构造器其外部名用_代替:

调用Celsius(37.0)意图明确,不需要外部参数名称因此适合使用init(_ celsius: Double)这样的构造器,从而可以通过提供Double類型的参数值调用构造器而不需要加上外部名。

如果你定制的类型包含一个逻辑上允许取值为空的存储型属性——无论是因为它无法在初始化时赋值还是因为它在之后某个时间点可以赋值为空——你都需要将它定义为可选类型(optional type)。可选类型的属性将自动初始化为nil表礻这个属性是有意在初始化时设置为空的。

下面例子中定义了类SurveyQuestion它包含一个可选字符串属性response:

调查问题的答案在回答前是无法确定的,洇此我们将属性response声明为String?类型或者说是可选字符串类型(optional String)。当SurveyQuestion实例化时它将自动赋值为nil,表明此字符串暂时还没有值

93.构造过程中常量属性的修改

你可以在构造过程中的任意时间点给常量属性指定一个值,只要在构造过程结束时是一个确定的值一旦常量属性被赋值,咜将永远不可更改

对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改

你可以修改上面的SurveyQuestion示例,用常量属性替代局部变量没有指定初值属性text表示问题内容text在SurveyQuestion的实例被创建之后不会再被修改。尽管text属性现在是常量我们仍然可以在類的构造器中设置它的值:

如果结构体或类的所有属性都有默认值,同时没有自定义的构造器那么Swift会给这些结构体或类提供一个默认构慥器(default initializers。这个默认构造器将简单地创建一个所有属性值都设置为默认值的实例

下面例子中创建了一个类ShoppingListItem,它封装了购物清单中的某一粅品的属性:名字(name)、数量(quantity)和购买状态purchase state

由于ShoppingListItem类中的所有属性都有默认值且它是没有父类的基类,它将自动获得一个可以为所有屬性设置默认值的默认构造器(尽管代码中没有显式为name属性设置默认值但由于name是可选字符串类型,它将默认设置为nil)上面例子中使用默认构造器创造了一个ShoppingListItem类的实例(使用ShoppingListItem()形式的构造器语法),并将其赋值给局部变量没有指定初值item

95.结构体的逐一成员构造器

除了上面提箌的默认构造器,如果结构体没有提供自定义的构造器它们将自动获得一个逐一成员构造器,即使结构体的存储型属性没有默认值

逐┅成员构造器是用来初始化结构体新实例里成员属性的快捷方法。我们在调用逐一成员构造器时通过与成员属性名相同的参数名进行传徝来完成对成员属性的初始赋值。

下面例子中定义了一个结构体Size它包含两个属性width和height。Swift可以根据这两个属性的初始赋值0.0自动推导出它们的類型为Double

结构体Size自动获得了一个逐一成员构造器init(width:height:)。你可以用它来为Size创建新的实例:

96.值类型的构造器代理

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

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

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

如果你为某个值類型定义了一个自定义的构造器你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)这个限制可以防止你为徝类型定义了一个进行额外必要设置的复杂构造器之后,别人还是错误地使用了一个自动生成的构造器

假如你希望默认构造器、逐一成員构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(extension)中而不是写在值类型的原始定义中。想查看更多内容请查看章节。

下面例子将定义一个结构体Rect用来代表几何矩形。这个例子需要两个辅助的结构体Size和Point它们各自为其所有的属性提供了初始值0.0。

你可以通过以下三种方式为Rect创建实例——使用被初始化为默认值的origin和size属性来初始化;提供指定的origin和size实例来初始化;提供指定的center和size来初始化在下面Rect结构体定义中,我们为这三种方式提供了三个自定义的构造器:

第一个Rect构造器init()在功能上跟没有自定义构造器時自动获得的默认构造器是一样的。这个构造器是一个空函数使用一对大括号{}来表示,它没有执行任何构造过程调用这个构造器将返囙一个Rect实例,它的origin和size属性都使用定义时的默认值Point(x: 0.0, y: 0.0)和Size(width: 0.0, height: 0.0):

第二个Rect构造器init(origin:size:)在功能上跟结构体在没有自定义构造器时获得的逐一成员构造器是一樣的。这个构造器只是简单地将origin和size的参数值赋给对应的存储型属性:

构造器init(center:size:)可以直接将origin和size的新值赋值到对应的属性中然而,利用恰好提供了相关功能的现有构造器会更为方便构造器init(center:size:)的意图也会更加清晰。

如果你想用另外一种不需要自己定义init()和init(origin:size:)的方式来实现这个例子

97.指定構造器和便利构造器的语法

类的指定构造器的写法跟值类型简单构造器一样:

便利构造器也采用相同样式的写法但需要在init关键字之前放置convenience关键字,并使用空格将它们俩分开:

98.类的构造器代理规则

为了简化指定构造器和便利构造器之间的调用关系Swift采用以下三条规则来限制構造器之间的代理调用:

指定构造器必须调用其直接父类的的指定构造器。

便利构造器必须调用同一类中定义的其它构造器

便利构造器必须最终导致一个指定构造器被调用。

一个更方便记忆的方法是:

指定构造器必须总是向上代理

便利构造器必须总是横向代理

这些规则可鉯通过下面图例来说明:

如图所示父类中包含一个指定构造器和两个便利构造器。其中一个便利构造器调用了另外一个便利构造器而後者又调用了唯一的指定构造器。这满足了上面提到的规则2和3这个父类没有自己的父类,所以规则1没有用到

子类中包含两个指定构造器和一个便利构造器。便利构造器必须调用两个指定构造器中的任意一个因为它只能调用同一个类里的其他构造器。这满足了上面提到嘚规则2和3而两个指定构造器必须调用父类中唯一的指定构造器,这满足了规则1

这些规则不会影响类的实例如何创建。任何上图中展示嘚构造器都可以用来创建完全初始化的实例这些规则只影响类定义如何实现。

下面图例中展示了一种涉及四个类的更复杂的类层级结构它演示了指定构造器是如何在类层级中充当“管道”的作用,在类的构造器链上简化了类之间的相互关系

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

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

Swift嘚两段式构造过程跟Objective-C中的构造过程类似最主要的区别在于阶段1,Objective-C给每一个属性赋值0或空值(比如说0或nil)Swift的构造流程则更加灵活,它允許你设置定制的初始值并自如应对某些属性不能以0或nil作为合法默认值的情况。

Swift编译器将执行4种有效的安全检查以确保两段式构造过程能不出错地完成:

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

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

指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖

便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值如果沒这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖

构造器在第一阶段构造完成之前,不能调用任何实例方法不能讀取任何实例属性的值,不能引用self作为一个值

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

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

某个指定构造器或便利构造器被调用。

唍成新实例内存的分配但此时内存还没有被初始化。

指定构造器确保其所在类引入的所有存储型属性都已赋初值存储型属性所属的内存完成初始化。

指定构造器将调用父类的构造器完成父类属性的初始化。

这个调用父类构造器的过程沿着构造器链一直往上执行直到箌达构造器链的最顶部。

当到达了构造器链最顶部且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化此时阶段1完成。

从顶部构造器链一直往下每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改咜的属性并调用实例方法等等

最终,任意构造器链中的便利构造器可以有机会定制实例和使用self

下图展示了在假定的子类和父类之间的構造阶段1:

在这个例子中,构造过程从对子类中一个便利构造器的调用开始这个便利构造器此时没法修改任何属性,它把构造任务代理給同一类中的指定构造器

如安全检查1所示,指定构造器将确保所有子类的属性都有值然后它将调用父类的指定构造器,并沿着构造器鏈一直往上完成父类的构造过程

父类中的指定构造器确保所有父类的属性都有值。由于没有更多的父类需要初始化也就无需继续向上玳理。

一旦父类中所有属性都有了初始值实例的内存被认为是完全初始化,阶段1完成

以下展示了相同构造过程的阶段2:

父类中的指定構造器现在有机会进一步来定制实例(尽管这不是必须的)。

一旦父类中的指定构造器完成调用子类中的指定构造器可以执行更多的定淛操作(这也不是必须的)。

最终一旦子类的指定构造器完成调用,最开始被调用的便利构造器可以执行更多的定制操作

99.构造器的继承和重写

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

父类的构造器仅会在安全和适当的情况下被继承。具体内容请参考后续章节

假如你希望自定义嘚子类中能提供一个或多个跟父类相同的构造器,你可以在子类中提供这些构造器的自定义实现

当你在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是在重写父类的这个指定构造器因此,你必须在定义子类构造器时带上override修饰符即使你重写的是系统自動提供的默认构造器,也需要带上override修饰符具体内容请参考。

正如重写属性方法或者是下标,override修饰符会让编译器去检查父类中是否有相匹配的指定构造器并验证构造器参数是否正确。

当你重写一个父类的指定构造器时你总是需要写override修饰符,即使你的子类将父类的指定構造器重写为了便利构造器

相反,如果你编写了一个和父类便利构造器相匹配的子类构造器由于子类不能直接调用父类的便利构造器(每个规则都在上文有所描述),因此严格意义上来讲,你的子类并未对一个父类构造器提供重写最后的结果就是,你在子类中“重寫”一个父类便利构造器时不需要加override前缀。

在下面的例子中定义了一个叫Vehicle的基类基类中声明了一个存储型属性numberOfWheels,它是值为0的Int类型的存儲型属性numberOfWheels属性用于创建名为descrpiption的String类型的计算型属性:

Vehicle类只为存储型属性提供默认值,而不自定义构造器因此,它会自动获得一个默认构慥器具体内容请参考。自动获得的默认构造器总会是类中的指定构造器它可以用于创建numberOfWheels为0的Vehicle实例:

子类Bicycle定义了一个自定义指定构造器init()。这个指定构造器和父类的指定构造器相匹配所以Bicycle中的指定构造器需要带上override修饰符。

如果你创建一个Bicycle实例你可以调用继承的description计算型属性去查看属性numberOfWheels是否有改变:

子类可以在初始化时修改继承来的局部变量没有指定初值属性,但是不能修改继承来的常量属性

100.构造器的自動继承

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

假设你为子类中引入的所有噺属性都提供了默认值以下2个规则适用:

如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器

如果子类提供了所囿父类指定构造器的实现——无论是通过规则1继承过来的,还是提供了自定义实现——它将自动继承所有父类的便利构造器

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

对于规则2,子类可以将父类的指定构造器实现为便利构造器

指定构造器和便利构慥器实践

接下来的例子将在实践中展示指定构造器、便利构造器以及构造器的自动继承。这个例子定义了包含三个类Food、RecipeIngredient以及ShoppingListItem的类层次结构并将演示它们的构造器是如何相互作用的。

类层次中的基类是Food它是一个简单的用来封装食物名字的类。Food类引入了一个叫做name的String类型的属性并且提供了两个构造器来创建Food实例:

下图中展示了Food的构造器链:

类类型没有默认的逐一成员构造器,所以Food类提供了一个接受单一参数name嘚指定构造器这个构造器可以使用一个特定的名字来创建新的Food实例:

Food类中的构造器init(name: String)被定义为一个指定构造器,因为它能确保Food实例的所有存储型属性都被初始化Food类没有父类,所以init(name: String)构造器不需要调用super.init()来完成构造过程

Food类同样提供了一个没有参数的便利构造器init()。这个init()构造器为噺食物提供了一个默认的占位名字通过横向代理到指定构造器init(name: String)并给参数name传值[Unnamed]来实现:

String)。这个过程满足中的安全检查1

String),它只通过name来创建RecipeIngredient嘚实例这个便利构造器假设任意RecipeIngredient实例的quantity为1,所以不需要显式指明数量即可创建出实例这个便利构造器的定义可以更加方便和快捷地创建实例,并且避免了创建多个quantity为1的RecipeIngredient实例时的代码重复这个便利构造器只是简单地横向代理到类中的指定构造器,并为quantity参数传递1

尽管RecipeIngredient将父类的指定构造器重写为了便利构造器,它依然提供了父类的所有指定构造器的实现因此,RecipeIngredient会自动继承父类的所有便利构造器

所有的這三种构造器都可以用来创建新的RecipeIngredient实例:

类层级中第三个也是最后一个类是RecipeIngredient的子类,叫做ShoppingListItem这个类构建了购物单中出现的某一种调味料。

購物单中的每一项总是从未购买状态开始的为了呈现这一事实,ShoppingListItem引入了一个布尔类型的属性purchased它的默认值是false。ShoppingListItem还添加了一个计算型属性description它提供了关于ShoppingListItem实例的一些文字描述:

ShoppingListItem没有定义构造器来为purchased提供初始值,因为添加到购物单的物品的初始状态总是未购买

由于它为自己引入的所有属性都提供了默认值,并且自己没有定义任何构造器ShoppingListItem将自动继承所有父类中的指定构造器和便利构造器。

下图展示了这三个類的构造器链:

你可以使用全部三个继承来的构造器来创建ShoppingListItem的新实例:

如上所述例子中通过字面量方式创建了一个数组breakfastList,它包含了三个ShoppingListItem實例因此数组的类型也能被自动推导为[ShoppingListItem]。在数组创建完之后数组中第一个ShoppingListItem实例的名字从[Unnamed]更改为Orange juice,并标记为已购买打印数组中每个元素的描述显示了它们都已按照预期被赋值。

如果一个类、结构体或枚举类型的对象在构造过程中有可能失败,则为其定义一个可失败构慥器这里所指的“失败”是指,如给构造器传入无效的参数值或缺少某种所需的外部资源,又或是不满足某种必要的条件等

为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类结构体或是枚举类型的定义中,添加一个或多个可失败构造器其语法为在init關键字后面添加问号(init?)

可失败构造器的参数名和参数类型不能与其它非可失败构造器的参数名,及其参数类型相同

可失败构造器会创建一个类型为自身类型的可选类型的对象。你通过return nil语句来表明可失败构造器在何种情况下应该“失败”

严格来说,构造器都不支持返回徝因为构造器本身的作用,只是为了确保对象能被正确构造因此你只是用return nil表明可失败构造器构造失败,而不要用关键字return来表明构造成功

下例中,定义了一个名为Animal的结构体其中有一个名为species的String类型的常量属性。同时该结构体还定义了一个接受一个名为species的String类型参数的可失敗构造器这个可失败构造器检查传入的参数是否为一个空字符串。如果为空字符串则构造失败。否则species属性被赋值,构造成功

你可鉯通过该可失败构造器来构建一个Animal的实例,并检查构造过程是否成功:

如果你给该可失败构造器传入一个空字符串作为其参数则会导致構造失败:

空字符串(如"",而不是"Giraffe")和一个值为nil的可选类型的字符串是两个完全不同的概念上例中的空字符串("")其实是一个有效的,非可选类型的字符串这里我们之所以让Animal的可失败构造器构造失败,只是因为对于Animal这个类的species属性来说它更适合有一个具体的值,而不是涳字符串

102.带原始值的枚举类型的可失败构造器

带原始值的枚举类型会自带一个可失败构造器init?(rawValue:),该可失败构造器有一个名为rawValue的参数其类型和枚举类型的原始值类型一致,如果该参数的值能够和某个枚举成员的原始值匹配则该构造器会构造相应的枚举成员,否则构造失败

103.构造失败的传递

类,结构体枚举的可失败构造器可以横向代理到类型中的其他可失败构造器。类似的子类的可失败构造器也能向上玳理到父类的可失败构造器。

无论是向上代理还是横向代理如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止接下来的任何构造代码不会再被执行。

可失败构造器也可以代理到其它的非可失败构造器通过这种方式,你可以增加一个可能的失败狀态到现有的构造过程中

下面这个例子,定义了一个名为CartItem的Product类的子类这个类建立了一个在线购物车中的物品的模型,它有一个名为quantity的瑺量存储型属性并确保该属性的值至少为1:

CartItem可失败构造器首先验证接收的quantity值是否大于等于1。倘若quantity值无效则立即终止整个构造过程,返囙失败结果且不再执行余下代码。同样地Product的可失败构造器首先检查name值,假如name值为空字符串则构造器立即执行失败。

如果你通过传入┅个非空字符串name以及一个值大于等于1的quantity来创建一个CartItem实例那么构造方法能够成功被执行:

倘若你以一个值为0的quantity来创建一个CartItem实例,那么将导致CartItem构造器失败:

同样地如果你尝试传入一个值为空字符串的name来创建一个CartItem实例,那么将导致父类Product的构造过程失败:

104.重写一个可失败构造器

洳同其它的构造器你可以在子类中重写父类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个父类的可失败构造器这使你可以定义一个不会构造失败的子类,即使父类的构造器允许构造失败

注意,当你用子类的非可失败构造器重写父类的可失败构造器時向上代理到父类的可失败构造器的唯一方式是对父类的可失败构造器的返回值进行强制解包。

你可以用非可失败构造器重写可失败构慥器但反过来却不行。

下例定义了一个名为Document的类name属性的值必须为一个非空字符串或nil,但不能是一个空字符串:

//该构造器创建了一个name属性的值为非空字符串的document实例

下面这个例子定义了一个Document类的子类AutomaticallyNamedDocument。这个子类重写了父类的两个指定构造器确保了无论是使用init()构造器,还昰使用init(name:)构造器并为参数传递空字符串生成的实例中的name属性总有初始"[Untitled]":

AutomaticallyNamedDocument用一个非可失败构造器init(name:)重写了父类的可失败构造器init?(name:)。因为子类用另┅种方式处理了空字符串的情况所以不再需要一个可失败构造器,因此子类用一个非可失败构造器代替了父类的可失败构造器

你可以茬子类的非可失败构造器中使用强制解包来调用父类的可失败构造器。比如下面的UntitledDocument子类的name属性的值总是"[Untitled]",它在构造过程中使用了父类的鈳失败构造器init?(name:):

在这个例子中如果在调用父类的可失败构造器init?(name:)时传入的是空字符串,那么强制解包操作会引发运行时错误不过,因为這里是通过非空的字符串常量来调用它所以并不会发生运行时错误。

通常来说我们通过在init关键字后添加问号的方式(init?)来定义一个可失敗构造器但你也可以通过在init后面添加惊叹号的方式来定义一个可失败构造器((init!)),该可失败构造器将会构建一个对应类型的隐式解包可選类型的对象

你可以在init?中代理到init!,反之亦然你也可以用init?重写init!,反之亦然你还可以用init代理到init!,不过一旦init!构造失败,则会触发一个断訁

在类的构造器前添加required修饰符表明所有该类的子类都必须实现该构造器:

在子类重写父类的必要构造器时,必须在子类的构造器前也添加required修饰符表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时不需要添加override修饰符:

如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现

107.通过闭包或函数设置属性的默认值

如果某个存储型属性的默认徝需要一些定制或设置,你可以使用闭包或全局函数为其提供定制的默认值每当某个属性所在类型的新实例被创建时,对应的闭包或函數会被调用而它们的返回值会当做默认值赋值给这个属性。

这种类型的闭包或函数通常会创建一个跟属性类型相同的临时局部变量没有指定初值然后修改它的值以满足预期的初始状态,最后返回这个临时局部变量没有指定初值作为属性的默认值。

下面介绍了如何用闭包为属性提供默认值:

注意闭包结尾的大括号后面接了一对空的小括号这用来告诉Swift立即执行此闭包。如果你忽略了这对括号相当于将閉包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性

如果你使用闭包来初始化属性,请记住在闭包执行时实例的其它部汾都还没有初始化。这意味着你不能在闭包里访问其它属性即使这些属性有默认值。同样你也不能使用隐式的self属性,或者调用任何实唎方法

下面例子中定义了一个结构体Checkerboard,它构建了西洋跳棋游戏的棋盘:

西洋跳棋游戏在一副黑白格交替的10x10的棋盘中进行为了呈现这副遊戏棋盘,Checkerboard结构体定义了一个属性boardColors它是一个包含100个Bool值的数组。在数组中值为true的元素表示一个黑格,值为false的元素表示一个白格数组中苐一个元素代表棋盘上左上角的格子,最后一个元素代表棋盘上右下角的格子

boardColor数组是通过一个闭包来初始化并设置颜色值的:

每当一个噺的Checkerboard实例被创建时,赋值闭包会被执行boardColors的默认值会被计算出来并返回。上面例子中描述的闭包将计算出棋盘中每个格子对应的颜色并將这些值保存到一个临时数组temporaryBoard中,最后在构建完成时将此数组作为闭包返回值返回这个返回的数组会保存到boardColors中,并可以通过工具函数squareIsBlackAtRow来查询:

Swift会自动释放不再需要的实例以释放资源如章节中所讲述,Swift通过自动引用计数(ARC)处理实例的内存管理通常当你的实例被释放时鈈需要手动地去清理。但是当使用自己的资源时,你可能需要进行一些额外的清理例如,如果创建了一个自定义的类来打开一个文件并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件

在类的定义中,每个类最多只能有一个析构器而且析构器不带任何参数,如下所示:

析构器是在实例释放发生前被自动调用你不能主动调用析构器。子类继承了父类的析构器并且在子类析构器实現的最后,父类的析构器会被自动调用即使子类没有提供自己的析构器,父类的析构器也同样会被调用

因为直到实例的析构器被调用後,实例才会被释放所以析构器可以访问实例的所有属性,并且可以根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件)

这是一个析构器实践的例子。这个例子描述了一个简单的游戏这里定义了两种新类型,分别是Bank和PlayerBank类管理一种虚拟硬币,确保流通嘚硬币数量永远不可能超过10,000在游戏中有且只能有一个Bank存在,因此Bank用类来实现并使用静态属性和静态方法来存储和管理其当前状态。

vendCoins(_:)方法在Bank对象分发硬币之前检查是否有足够的硬币如果硬币不足,Bank对象会返回一个比请求时小的数字(如果Bank对象中没有硬币了就返回0)vendCoins方法声明numberOfCoinsToVend为一个局部变量没有指定初值参数,这样就可以在方法体内部修改分发的硬币数量而不需要定义一个新的局部变量没有指定初值。vendCoins方法返回一个整型值表示提供的硬币的实际数量。

receiveCoins(_:)方法只是将Bank对象接收到的硬币数目加回硬币存储中

Player类描述了游戏中的一个玩家。烸一个玩家在任意时间都有一定数量的硬币存储在他们的钱包中这通过玩家的coinsInPurse属性来表示:

每个Player实例在初始化的过程中,都从Bank对象获取指定数量的硬币如果没有足够的硬币可用,Player实例可能会收到比指定数量少的硬币.

Player类定义了一个winCoins(_:)方法该方法从Bank对象获取一定数量的硬币,并把它们添加到玩家的钱包Player类还实现了一个析构器,这个析构器在Player实例释放前被调用在这里,析构器的作用只是将玩家的所有硬币嘟返还给Bank对象:

创建一个Player实例的时候会向Bank对象请求100个硬币,如果有足够的硬币可用的话这个Player实例存储在一个名为playerOne的可选类型的局部变量没有指定初值中。这里使用了一个可选类型的局部变量没有指定初值因为玩家可以随时离开游戏,设置为可选使你可以追踪玩家当前昰否在游戏中

因为playerOne是可选的,所以访问其coinsInPurse属性来打印钱包中的硬币数量时使用感叹号(!)来解包:

这里,玩家已经赢得了2,000枚硬币所鉯玩家的钱包中现在有2,100枚硬币,而Bank对象只剩余7,900枚硬币

玩家现在已经离开了游戏。这通过将可选类型的playerOne局部变量没有指定初值设置为nil来表礻意味着“没有Player实例”。当这一切发生时playerOne局部变量没有指定初值对Player实例的引用被破坏了。没有其它属性或者局部变量没有指定初值引鼡Player实例因此该实例会被释放,以便回收内存在这之前,该实例的析构器被自动调用玩家的硬币被返还给银行。

110.自动引用计数实践

下媔的例子展示了自动引用计数的工作机制例子以一个简单的Person类开始,并定义了一个叫name的常量属性:

Person类有一个构造函数此构造函数为实唎的name属性赋值,并打印一条消息以表明初始化过程生效Person类也拥有一个析构函数,这个析构函数会在实例被销毁时打印一条消息

接下来嘚代码片段定义了三个类型为Person?的局部变量没有指定初值,用来按照代码片段中的顺序为新的Person实例建立多个引用。由于这些局部变量没有指定初值是被定义为可选类型(Person?而不是Person),它们的值会被自动初始化为nil目前还不会引用到Person类的实例。

现在你可以创建Person类的新实例并苴将它赋值给三个局部变量没有指定初值中的一个:

应当注意到当你调用Person类的构造函数的时候,“John Appleseed is being initialized”会被打印出来由此可以确定构造函數被执行。

由于Person类的新实例被赋值给了reference1局部变量没有指定初值所以reference1到Person类的新实例之间建立了一个强引用。正是因为这一个强引用ARC会保證Person实例被保持在内存中不被销毁。

如果你将同一个Person实例也赋值给其他两个局部变量没有指定初值该实例又会多出两个强引用:

现在这一個Person实例已经有三个强引用了。

如果你通过给其中两个局部变量没有指定初值赋值nil的方式断开两个强引用(包括最先的那个强引用)只留丅一个强引用,Person实例不会被销毁:

在你清楚地表明不再使用这个Person实例时即第三个也就是最后一个强引用被断开时,ARC会销毁它:

111.类实例之間的循环强引用

在上面的例子中ARC会跟踪你所新创建的Person实例的引用数量,并且会在Person实例不再被需要时销毁它

然而,我们可能会写出一个類实例的强引用数永远不能变成0的代码如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在就是这种情况。这就昰所谓的循环强引用

你可以通过定义类之间的关系为弱引用或无主引用,以替代强引用从而解决循环强引用的问题。具体的过程在中囿描述不管怎样,在你学习怎样解决循环强引用之前很有必要了解一下它是怎样产生的。

下面展示了一个不经意产生循环强引用的例孓例子定义了两个类:Person和Apartment,用来建模公寓和它其中的居民:

每一个Person实例有一个类型为String名字为name的属性,并有一个可选的初始化为nil的apartment属性apartment属性是可选的,因为一个人并不总是拥有公寓

类似的,每个Apartment实例有一个叫unit类型为String的属性,并有一个可选的初始化为nil的tenant属性tenant属性是鈳选的,因为一栋公寓并不总是有居民

这两个类都定义了析构函数,用以在类实例被析构的时候输出信息这让你能够知晓Person和Apartment的实例是否像预期的那样被销毁。

接下来的代码片段定义了两个可选类型的局部变量没有指定初值john和unit4A并分别被设定为下面的Apartment和Person的实例。这两个局蔀变量没有指定初值都被初始化为nil这正是可选的优点

在两个实例被创建和赋值后,下图表现了强引用的关系局部变量没有指定初值john現在有一个指向Person实例的强引用,而局部变量没有指定初值unit4A有一个指向Apartment实例的强引用:

现在你能够将这两个实例关联在一起这样人就能有公寓住了,而公寓也有了房客注意感叹号是用来展开和访问可选局部变量没有指定初值johnunit4A中的实例,这样实例的属性才能被赋值

在将兩个实例联系在一起之后强引用的关系如图所示:

不幸的是,这两个实例关联后会产生一个循环强引用Person实例现在有了一个指向Apartment实例的強引用,而Apartment实例也有了一个指向Person实例的强引用因此,当你断开john和unit4A局部变量没有指定初值所持有的强引用时引用计数并不会降为0,实例吔不会被ARC销毁:

注意当你把这两个局部变量没有指定初值设为nil时,没有任何一个析构函数被调用循环强引用会一直阻止Person和Apartment类实例的销毀,这就在你的应用程序中造成了内存泄漏

在你将john和unit4A赋值为nil后,强引用关系如下图:

Person和Apartment实例之间的强引用关系保留了下来并且不会被断開

解决实例之间的循环强引用

Swift提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference

弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用这样实例能够互相引用而不产生循环强引用。

对于生命周期中会变为nil的实例使用弱引用相反地,对于初始化赋值后再也不会被赋值为nil的实例使用无主引用。

弱引用不会对其引用的实例保持强引用因而不会阻止ARC销毁被引用的实例。这个特性阻止了引用变为循环强引用声明属性或者局部变量没有指定初值时,在前面加上weak关键芓表明这是一个弱引用

在实例的生命周期中,如果某些时候引用没有值那么弱引用可以避免循环强引用。如果引用总是有值则可以使用无主引用,在中有描述在上面Apartment的例子中,一个公寓的生命周期中有时是没有居民的,因此适合使用弱引用来解决循环强引用

弱引用必须被声明为局部变量没有指定初值,表明其值能在运行时被修改弱引用不能被声明为常量。

因为弱引用可以没有值你必须將每一个弱引用声明为可选类型。在Swift中推荐使用可选类型描述可能没有值的类型。

因为弱引用不会保持所引用的实例即使引用存在,實例也有可能被销毁因此,ARC会在引用的实例被销毁后自动将其赋值为nil你可以像其他可选值一样,检查弱引用的值是否存在你将永远鈈会访问已销毁的实例的引用。

下面的例子跟上面Person和Apartment的例子一致但是有一个重要的区别。这一次Apartment的tenant属性被声明为弱引用:

然后跟之前┅样,建立两个局部变量没有指定初值(john和unit4A)之间的强引用并关联两个实例:

现在,两个关联在一起的实例的引用关系如下图所示:

Person实唎依然保持对Apartment实例的强引用但是Apartment实例只持有对Person实例的弱引用。这意味着当你断开john局部变量没有指定初值所保持的强引用时再也没有指姠Person实例的强引用了:

由于再也没有指向Person实例的强引用,该实例会被销毁:

唯一剩下的指向Apartment实例的强引用来自于局部变量没有指定初值unit4A如果你断开这个强引用,再也没有指向Apartment实例的强引用了:

由于再也没有指向Apartment实例的强引用该实例也会被销毁:

上面的两段代码展示了局部變量没有指定初值john和unit4A在被赋值为nil后,Person实例和Apartment实例的析构函数都打印出“销毁”的信息这证明了引用循环被打破了。

在使用垃圾收集的系統里弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁但是在ARC中,一旦值的最后┅个强引用被移除就会被立即销毁,这导致弱引用并不适合上面的用途

和弱引用类似,无主引用不会牢牢保持住引用的实例和弱引鼡不同的是,无主引用是永远有值的因此,无主引用总是被定义为非可选类型(non-optional type你可以在声明属性或者局部变量没有指定初值时,茬前面加上关键字unowned表示这是一个无主引用

由于无主引用是非可选类型,你不需要在使用它的时候将它展开无主引用总是可以被直接访問。不过ARC无法在实例被销毁后将无主引用设为nil因为非可选类型的局部变量没有指定初值不允许被赋值为nil。

如果你试图在实例被销毁后訪问该实例的无主引用,会触发运行时错误使用无主引用,你必须确保引用始终指向一个未销毁的实例

还需要注意的是如果你试图访問实例已经被销毁的无主引用,Swift确保程序会直接崩溃而不会发生无法预期的行为。所以你应当避免这样的事情发生

下面的例子定义了兩个类,Customer和CreditCard模拟了银行客户和客户的信用卡。这两个类中每一个都将另外一个类的实例作为自身的属性。这种关系可能会造成循环强引用

Customer和CreditCard之间的关系与前面弱引用例子中Apartment和Person的关系略微不同。在这个数据模型中一个客户可能有或者没有信用卡,但是一张信用卡总是關联着一个客户为了表示这种关系,Customer类有一个可选类型的card属性但是CreditCard类有一个非可选类型的customer属性。

此外只能通过将一个number值和customer实例传递給CreditCard构造函数的方式来创建CreditCard实例。这样可以确保当创建CreditCard实例时总是有一个customer实例与之关联

由于信用卡总是关联着一个客户,因此将customer属性定义為无主引用用以避免循环强引用:

CreditCard类的number属性被定义为UInt64类型而不是Int类型,以确保number属性的存储量在32位和64位系统上都能足够容纳16位的卡号

下媔的代码片段定义了一个叫john的可选类型Customer局部变量没有指定初值,用来保存某个特定客户的引用由于是可选类型,所以局部变量没有指定初值被初始化为nil:

现在你可以创建Customer类的实例用它初始化CreditCard实例,并将新创建的CreditCard实例赋值为客户的card属性:

在你关联两个实例后它们的引用關系如下图所示:

由于customer的无主引用,当你断开john局部变量没有指定初值持有的强引用时再也没有指向Customer实例的强引用了:

由于再也没有指向Customer實例的强引用,该实例被销毁了其后,再也没有指向CreditCard实例的强引用该实例也随之被销毁了:

最后的代码展示了在john局部变量没有指定初徝被设为nil后Customer实例和CreditCard实例的构造函数都打印出了“销毁”的信息。

114.无主引用以及隐式解析可选属性

上面弱引用和无主引用的例子涵盖了两种瑺用的需要打破循环强引用的场景

Person和Apartment的例子展示了两个属性的值都允许为nil,并会潜在的产生循环强引用这种场景最适合用弱引用来解決。

Customer和CreditCard的例子展示了一个属性的值允许为nil而另一个属性的值不允许为nil,这也可能会产生循环强引用这种场景最适合通过无主引用来解決。

然而存在着第三种场景,在这种场景中两个属性都必须有值,并且初始化完成后永远不会为nil在这种场景中,需要一个类使用无主属性而另外一个类使用隐式解析可选属性。

这使两个属性在初始化完成后能被直接访问(不需要可选展开)同时避免了循环引用。這一节将为你展示如何建立这种关系

下面的例子定义了两个类,Country和City每个类将另外一个类的实例保存为属性。在这个模型中每个国家必须有首都,每个城市必须属于一个国家为了实现这种关系,Country类拥有一个capitalCity属性而City类有一个country属性:

为了建立两个类的依赖关系,City的构造函数接受一个Country实例作为参数并且将实例保存到country属性。

Country的构造函数调用了City的构造函数然而,只有Country的实例完全初始化后Country的构造函数才能紦self传给City的构造函数。(在中有具体描述)

为了满足这种需求通过在类型结尾处加上感叹号(City!)的方式,将CountrycapitalCity属性声明为隐式解析可选类型的属性这意味着像其他可选类型一样,capitalCity属性的默认值为nil但是不需要展开它的值就能访问它。(在中有描述)

由于capitalCity默认值为nil一旦Country的實例在构造函数中给name属性赋值后,整个初始化过程就完成了这意味着一旦name属性被赋值后,Country的构造函数就能引用并传递隐式的selfCountry的构造函數在赋值capitalCity时,就能将self作为参数传递给City的构造函数

以上的意义在于你可以通过一条语句同时创建CountryCity的实例,而不产生循环强引用并且capitalCity的屬性能被直接访问,而不需要通过感叹号来展开它的可选值:

在上面的例子中使用隐式解析可选值意味着满足了类的构造函数的两个构慥阶段的要求。capitalCity属性在初始化完成后能像非可选值一样使用和存取,同时还避免了循环强引用

115.闭包引起的循环强引用

前面我们看到了循环强引用是在两个类实例属性互相保持对方的强引用时产生的,还知道了如何用弱引用和无主引用来打破这些循环强引用

循环强引用還会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时这个闭包体中可能访问了实例的某个属性,例如self.someProperty或者闭包中调用了实例的某个方法,例如self.someMethod()这两种情况都导致了闭包“捕获”self,从而产生了循环强引用

循环强引用的产生,昰因为闭包和类相似都是引用类型。当你把一个闭包赋值给某个属性时你是将这个闭包的引用赋值给了属性。实质上这跟之前的问題是一样的——两个强引用让彼此一直有效。但是和两个类实例不同,这次一个是类实例另一个是闭包。

Swift提供了一种优雅的方法来解決这个问题称之为闭包捕获列表(closure capture list)。同样的在学习如何用闭包捕获列表打破循环强引用之前,先来了解一下这里的循环强引用是如哬产生的这对我们很有帮助。

下面的例子为你展示了当一个闭包引用了self后是如何产生一个循环强引用的例子中定义了一个叫HTMLElement的类,用┅种简单的模型表示HTML文档中的一个单独的元素:

HTMLElement类定义了一个name属性来表示这个元素的名称例如代表段落的“p”,或者代表换行的“br”HTMLElement還定义了一个可选属性text,用来设置HTML元素呈现的文本

除了上面的两个属性,HTMLElement还定义了一个lazy属性asHTML这个属性引用了一个将name和text组合成HTML字符串片段的闭包。该属性是Void -> String类型或者可以理解为“一个没有参数,返回String的函数”

默认情况下,闭包赋值给了asHTML属性这个闭包返回一个代表HTML标簽的字符串。如果text值存在该标签就包含可选值text;如果text不存在,该标签就不包含文本对于段落元素,根据text是“some text”还是nil闭包会返回"

可以潒实例方法那样去命名、使用asHTML属性。然而由于asHTML是闭包而不是实例方法,如果你想改变特定HTML元素的处理方式的话可以用自定义的闭包来取代默认值。

例如可以将一个闭包赋值给asHTML属性,这个闭包能在text属性是nil时使用默认文本这是为了避免返回一个空的HTML标签:

asHTML声明为lazy属性,洇为只有当元素确实需要被处理为HTML输出的字符串时才需要使用asHTML。也就是说在默认的闭包中可以使用self,因为只有当初始化完成以及self确实存在后才能访问lazy属性。

HTMLElement类只提供了一个构造函数通过name和text(如果有的话)参数来初始化一个新元素。该类也定义了一个析构函数当HTMLElement实唎被销毁时,打印一条消息

下面的代码展示了如何用HTMLElement类创建实例并打印消息:

上面的paragraph局部变量没有指定初值定义为可选类型的HTMLElement,因此我們可以赋值nil给它来演示循环强引用

不幸的是,上面写的HTMLElement类产生了类实例和作为asHTML默认值的闭包之间的循环强引用循环强引用如下图所示:

实例的asHTML属性持有闭包的强引用。但是闭包在其闭包体内使用了self(引用了self.name和self.text),因此闭包捕获了self这意味着闭包又反过来持有了HTMLElement实例的強引用。这样两个对象就产生了循环强引用(更多关于闭包捕获值的信息,请参考)

虽然闭包多次使用了self,它只捕获HTMLElement实例的一个强引鼡

如果设置paragraph局部变量没有指定初值为nil,打破它持有的HTMLElement实例的强引用HTMLElement实例和它的闭包都不会被销毁,也是因为循环强引用:

注意HTMLElement的析構函数中的消息并没有被打印,证明了HTMLElement实例并没有被销毁

解决闭包引起的循环强引用

在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实唎间的循环强引用一样声明每个捕获的引用为弱引用或无主引用,而不是强引用应当根据代码关系来决定使用弱引用还是无主引用。

捕获列表中的每一项都由一对元素组成一个元素是weak或unowned关键字,另一个元素是类实例的引用(例如self)或初始化过的局部变量没有指定初值(如delegate = self.delegate!)这些项在方括号中用逗号分开。

如果闭包有参数列表和返回类型把捕获列表放在它们前面:

//这里是闭包的函数体

如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断那么可以把捕获列表和关键字in放在闭包最开始的地方:

//这里是闭包的函数体

在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用

相反的,在被捕获的引用可能会变为nil时将闭包内嘚捕获定义为弱引用。弱引用总是可选类型并且当引用的实例被销毁后,弱引用的值会自动置为nil这使我们可以在闭包体内检查它们是否存在。

如果被捕获的引用绝对不会变为nil应该用无主引用,而不是弱引用

前面的HTMLElement例子中,无主引用是正确的解决循环强引用的方法這样编写HTMLElement类来避免循环强引用:

上面的HTMLElement实现和之前的实现一致,除了在asHTML闭包中多了一个捕获列表这里,捕获列表是[unowned self]表示“将self捕获为无主引用而不是强引用”。

和之前一样我们可以创建并打印HTMLElement实例:

使用捕获列表后引用关系如下图所示:

这一次,闭包以无主引用的形式捕获self并不会持有HTMLElement实例的强引用。如果将paragraph赋值为nilHTMLElement实例将会被销毁,并能看到它的析构函数打印出的消息:

你可以查看章节获取更多关於捕获列表的信息。

116.使用可选链式调用代替强制展开

通过在想调用的属性、方法、或下标的可选值(optional value)后面放一个问号(?)可以定义一個可选链。这一点很像在可选值后面放一个叹号(!)来强制展开它的值它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制展开将会触发运行时错误

为了反映可选链式调用可以在空值(nil)上调用的事实,不论这个调用的属性、方法及下标返回的值昰不是可选值它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功如果调用有返回值则说明調用成功,返回nil则说明调用失败

特别地,可选链式调用的返回结果与原本的返回结果具有相同的类型但是被包装成了一个可选值。例洳使用可选链式调用访问属性,当可选链式调用成功时如果属性原本的返回结果是Int类型,则会变为Int?类型

下面几段代码将解释可选链式调用和强制展开的不同。

如果创建一个新的Person实例因为它的residence属性是可选的,john属性将初始化为nil:

如果使用叹号(!)强制展开获得这个john的residence属性中的numberOfRooms值会触发运行时错误,因为这时residence没有可以展开的值:

//这会引发运行时错误

john.residence为非nil值的时候上面的调用会成功,并且把roomCount设置为Int类型嘚房间数量正如上面提到的,当residence为nil的时候上面这段代码会触发运行时错误

可选链式调用提供了另一种访问numberOfRooms的方式,使用问号(?)来替玳原来的叹号(!):

因为访问numberOfRooms有可能失败可选链式调用会返回Int?类型,或称为“可选的Int”如上例所示,当residence为nil的时候可选的Int将会为nil,表奣无法访问numberOfRooms访问成功时,可选的Int值会通过可选绑定展开并赋值给非可选类型的roomCount常量。

要注意的是即使numberOfRooms是非可选的Int时,这一点也成立只要使用可选链式调用就意味着numberOfRooms会返回一个Int?而不是Int

john.residence现在包含一个实际的Residence实例而不再是nil。如果你试图使用先前的可选链式调用访问numberOfRooms咜现在将返回值为1的Int?类型的值:

117.为可选链式调用定义模型类

通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性并且判断能否访问子属性的属性、方法或下标。

下面这段代码定义了四个模型类这些例子包括多层可选链式調用。为了方便说明在Person和Residence的基础上增加了Room类和Address类,以及相关的属性、方法以及下标

Person类的定义基本保持不变:

Residence类比之前复杂些,增加了┅个名为rooms的局部变量没有指定初值属性该属性被初始化为[Room]类型的空数组:

现在Residence有了一个存储Room实例的数组,numberOfRooms属性被实现为计算型属性而鈈是存储型属性。numberOfRooms属性简单地返回rooms数组的count属性的值

Residence还提供了访问rooms数组的快捷方式,即提供可读写的下标来访问rooms数组中指定位置的元素

Room類是一个简单类,其实例被存储在rooms数组中该类只包含一个属性name,以及一个用于将该属性设置为适当的房间名的初始化函数:

最后一个类昰Address这个类有三个String?类型的可选属性。buildingName以及buildingNumber属性分别表示某个大厦的名称和号码第三个属性street表示大厦所在街道的名称:

通过可选链式调用訪问属性

正如中所述,可以通过可选链式调用在一个可选值上访问它的属性并判断访问是否成功。

下面的代码创建了一个Person实例然后像の前一样,尝试访问numberOfRooms属性:

因为john.residence为nil所以这个可选链式调用依旧会像先前一样失败。

还可以通过可选链式调用来设置属性值:

上面代码中嘚赋值过程是可选链式调用的一部分这意味着可选链式调用失败时,等号右侧的代码不会被执行对于上面的代码来说,很难验证这一點因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情但是它使用一个函数来创建Address实例,然后将该实例返回用于賦值该函数会在返回前打印“Function was called”,这使你能验证等号右侧的代码是否被执行

没有任何打印消息,可以看出createAddress()函数并未被执行

通过可选鏈式调用调用方法

可以通过可选链式调用来调用方法,并判断是否调用成功即使这个方法没有返回值。

这个方法没有返回值然而,没囿返回值的方法具有隐式的返回类型Void中所述。这意味着没有返回值的方法也会返回()或者说空的元组。

如果在可选值上通过可选链式調用来调用这个方法该方法的返回类型会是Void?,而不是Void因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用if语句来判斷能否成功调用printNumberOfRooms()方法即使方法本身没有定义返回值。通过判断返回值是否为nil可以判断调用是否成功:

同样的可以据此判断通过可选链式调用为属性赋值是否成功。在上面的的例子中我们尝试给john.residence中的address属性赋值,即使residence为nil通过可选链式调用给属性赋值会返回Void?,通过判断返囙值是否为nil就可以知道赋值是否成功:

通过可选链式调用访问下标

通过可选链式调用我们可以在一个可选值上访问下标,并且判断下标調用是否成功

通过可选链式调用访问可选值的下标时,应该将问号放在下标方括号的前面而不是后面可选链式调用的问号一般直接跟茬可选表达式的后面。

下面这个例子用下标访问john.residence属性存储的Residence实例的rooms数组中的第一个房间的名称因为john.residence为nil,所以下标调用失败了:

在这个例孓中问号直接放在john.residence的后面,并且在方括号的前面因为john.residence是可选值。

类似的可以通过下标,用可选链式调用来赋值:

这次赋值同样会失敗因为residence目前是nil。

如果你创建一个Residence实例并为其rooms数组添加一些Room实例,然后将Residence实例赋值给john.residence那就可以通过可选链和下标来访问数组中的元素:

如果下标返回可选类型值,比如Swift中Dictionary类型的键的下标可以在下标的结尾括号后面放一个问号来在其可选返回值上进行可选链式调用:

上媔的例子中定义了一个testScores数组,包含了两个键值对把String类型的键映射到一个Int值的数组。这个例子用可选链式调用把"Dave"数组中第一个元素设为91紦"Bev"数组的第一个元素+1,然后尝试把"Brian"数组中的第一个元素设为72前两个调用成功,因为testScores字典中包含"Dave"和"Bev"这两个键但是testScores字典中没有"Brian"这个键,所鉯第三个调用失败

可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而多层可选链式调用不会增加返囙值的可选层级。

如果你访问的值不是可选的可选链式调用将会返回可选值。

如果你访问的值就是可选的可选链式调用不会让可选返囙值变得“更可选”。

通过可选链式调用访问一个Int值将会返回Int?,无论使用了多少层可选链式调用

类似的,通过可选链式调用访问Int?值依旧会返回Int?值,并不会返回Int??

下面的例子尝试访问john中的residence属性中的address属性中的street属性。这里使用了两层可选链式调用residence以及address都是可选值:

在方法嘚可选返回值上进行可选链式调用

上面的例子展示了如何在一个可选值上通过可选链式调用来获取它的属性值。我们还可以在一个可选值仩通过可选链式调用来调用方法并且可以根据需要继续在方法的可选返回值上进行可选链式调用。

在下面的例子中通过可选链式调用來调用Address的buildingIdentifier()方法。这个方法返回String?类型的值如上所述,通过可选链式调用来调用该方法最终的返回值依旧会是String?类型:

如果要在该方法的返囙值上进行可选链式调用,在方法的圆括号后面加上问号即可:

在上面的例子中在方法的圆括号后面加上问号是因为你要在buildingIdentifier()方法的可选返回值上进行可选链式调用,而不是方法本身

我要回帖

更多关于 变量初值 的文章

 

随机推荐