Tag Archives: Swift

Swift中文教程(23)高级运算符

转载自letsswift.com

除了基本操作符中所讲的运算符,Swift还有许多复杂的高级运算符,包括了C语和Objective-C中的位运算符和移位运算。

不同于C语言中的数值计算,Swift的数值计算默认是不可溢出的。溢出行为会被捕获并报告为错误。你是故意的?好吧,你可以使用Swift为你准备的另一套默认允许溢出的数值运算符,如可溢出加&+。所有允许溢出的运算符都是以&开始的。

自定义的结构,类和枚举,是否可以使用标准的运算符来定义操作?当然可以!在Swift中,你可以为你创建的所有类型定制运算符的操作。

可定制的运算符并不限于那些预设的运算符,自定义有个性的中置,前置,后置及赋值运算符,当然还有优先级和结合性。这些运算符的实现可以运用预设的运算符,也可以运用之前定制的运算符。

位运算符

位操作符通常在诸如图像处理和创建设备驱动等底层开发中使用,使用它可以单独操作数据结构中原始数据的比特位。在使用一个自定义的协议进行通信的时候,运用位运算符来对原始数据进行编码和解码也是非常有效的。

Swift支持如下所有C语言的位运算符:

按位取反运算符

按位取反运算符~对一个操作数的每一位都取反。

Art/bitwiseNOT_2x.png

这个运算符是前置的,所以请不加任何空格地写着操作数之前。

let initialBits:UInt8=0b00001111
let invertedBits =~initialBits  // 等于 0b11110000

UInt8是8位无符整型,可以存储0~255之间的任意数。这个例子初始化一个整型为二进制值00001111(前4位为0,后4位为1),它的十进制值为15

使用按位取反运算~initialBits操作,然后赋值给invertedBits这个新常量。这个新常量的值等于所有位都取反的initialBits,即1变成00变成1,变成了11110000,十进制值为240

按位与运算符

按位与运算符对两个数进行操作,然后返回一个新的数,这个数的每个位都需要两个输入数的同一位都为1时才为1。

Art/bitwiseAND_2x.png

以下代码,firstSixBitslastSixBits中间4个位都为1。对它俩进行按位与运算后,就得到了00111100,即十进制的60

let firstSixBits:UInt8=0b11111100
let lastSixBits:UInt8=0b00111111
let middleFourBits = firstSixBits & lastSixBits  // 等于 00111100
按位或运算

按位或运算符|比较两个数,然后返回一个新的数,这个数的每一位设置1的条件是两个输入数的同一位都不为0(即任意一个为1,或都为1)。

Art/bitwiseOR_2x.png

如下代码,someBitsmoreBits在不同位上有1。按位或运行的结果是11111110,即十进制的254

let someBits:UInt8=0b10110010
let moreBits:UInt8=0b01011110
let combinedbits = someBits | moreBits  // 等于 11111110
按位异或运算符

按位异或运算符^比较两个数,然后返回一个数,这个数的每个位设为1的条件是两个输入数的同一位不同,如果相同就设为0

Art/bitwiseXOR_2x.png

以下代码,firstBitsotherBits都有一个1跟另一个数不同的。所以按位异或的结果是把它这些位置为1,其他都置为0

let firstBits:UInt8=0b00010100
let otherBits:UInt8=0b00000101
let outputBits = firstBits ^ otherBits  // 等于 00010001
按位左移/右移运算符

左移运算符<<和右移运算符>>会把一个数的所有比特位按以下定义的规则向左或向右移动指定位数。

按位左移和按位右移的效果相当把一个整数乘于或除于一个因子为2的整数。向左移动一个整型的比特位相当于把这个数乘于2,向右移一位就是除于2

无符整型的移位操作

对无符整型的移位的效果如下:

已经存在的比特位向左或向右移动指定的位数。被移出整型存储边界的的位数直接抛弃,移动留下的空白位用零0来填充。这种方法称为逻辑移位。

以下这张把展示了 11111111 << 1(11111111向左移1位),和 11111111 >> 1(11111111向右移1位)。蓝色的是被移位的,灰色是被抛弃的,橙色的0是被填充进来的。

Art/bitshiftUnsigned_2x.png

let shiftBits:UInt8=4// 即二进制的00000100
shiftBits <<1// 00001000
shiftBits <<2// 00010000
shiftBits <<5// 10000000
shiftBits <<6// 00000000
shiftBits >>2// 00000001

你可以使用移位操作进行其他数据类型的编码和解码。

let pink:UInt32=0xCC6699
let redComponent =(pink &0xFF0000)>>16// redComponent 是 0xCC, 即 204
let greenComponent =(pink &0x00FF00)>>8// greenComponent 是 0x66, 即 102
let blueComponent = pink &0x0000FF// blueComponent 是 0x99, 即 153

这个例子使用了一个UInt32的命名为pink的常量来存储层叠样式表CSS中粉色的颜色值,CSS颜色#CC6699在Swift用十六进制0xCC6699来表示。然后使用按位与(&)和按位右移就可以从这个颜色值中解析出红(CC),绿(66),蓝(99)三个部分。

0xCC66990xFF0000进行按位与&操作就可以得到红色部分。0xFF0000中的0了遮盖了OxCC6699的第二和第三个字节,这样6699被忽略了,只留下0xCC0000

然后,按向右移动16位,即 >> 16。十六进制中每两个字符是8比特位,所以移动16位的结果是把0xCC0000变成0x0000CC。这和0xCC是相等的,都是十进制的204

同样的,绿色部分来自于0xCC66990x00FF00的按位操作得到0x006600。然后向右移动8們,得到0x66,即十进制的102

最后,蓝色部分对0xCC66990x0000FF进行按位与运算,得到0x000099,无需向右移位了,所以结果就是0x99,即十进制的153

有符整型的移位操作

有符整型的移位操作相对复杂得多,因为正负号也是用二进制位表示的。(这里举的例子虽然都是8位的,但它的原理是通用的。)

有符整型通过第1个比特位(称为符号位)来表达这个整数是正数还是负数。0代表正数,1代表负数。

其余的比特位(称为数值位)存储其实值。有符正整数和无符正整数在计算机里的存储结果是一样的,下来我们来看+4内部的二进制结构。

Art/bitshiftSignedFour_2x.png

符号位为0,代表正数,另外7比特位二进制表示的实际值就刚好是4

负数呢,跟正数不同。负数存储的是2的n次方减去它的绝对值,n为数值位的位数。一个8比特的数有7个数值位,所以是2的7次方,即128。

我们来看-4存储的二进制结构。

Art/bitshiftSignedMinusFour_2x.png

现在符号位为1,代表负数,7个数值位要表达的二进制值是124,即128 – 4。

Art/bitshiftSignedMinusFourValue_2x.png

负数的编码方式称为二进制补码表示。这种表示方式看起来很奇怪,但它有几个优点。

首先,只需要对全部8个比特位(包括符号)做标准的二进制加法就可以完成 -1 + -4 的操作,忽略加法过程产生的超过8个比特位表达的任何信息。

Art/bitshiftSignedAddition_2x.png

第二,由于使用二进制补码表示,我们可以和正数一样对负数进行按位左移右移的,同样也是左移1位时乘于2,右移1位时除于2。要达到此目的,对有符整型的右移有一个特别的要求:

对有符整型按位右移时,使用符号位(正数为0,负数为1)填充空白位。

Art/bitshiftSigned_2x.png

这就确保了在右移的过程中,有符整型的符号不会发生变化。这称为算术移位。

正因为正数和负数特殊的存储方式,向右移位使它接近于0。移位过程中保持符号会不变,负数在接近0的过程中一直是负数。

溢出运算符

默认情况下,当你往一个整型常量或变量赋于一个它不能承载的大数时,Swift不会让你这么干的,它会报错。这样,在操作过大或过小的数的时候就很安全了。

例如,Int16整型能承载的整数范围是-3276832767,如果给它赋上超过这个范围的数,就会报错:

var potentialOverflow =Int16.max
// potentialOverflow 等于 32767, 这是 Int16 能承载的最大整数
potentialOverflow +=1// 噢, 出错了

对过大或过小的数值进行错误处理让你的数值边界条件更灵活。

当然,你有意在溢出时对有效位进行截断,你可采用溢出运算,而非错误处理。Swfit为整型计算提供了5个&符号开头的溢出运算符。

  • 溢出加法 &+
  • 溢出减法 &-
  • 溢出乘法 &*
  • 溢出除法 &/
  • 溢出求余 &%
值的上溢出

下面例子使用了溢出加法&+来解剖的无符整数的上溢出

var willOverflow =UInt8.max
// willOverflow 等于UInt8的最大整数 255
willOverflow = willOverflow &+1// 这时候 willOverflow 等于 0

willOverflowInt8所能承载的最大值255(二进制11111111),然后用&+加1。然后UInt8就无法表达这个新值的二进制了,也就导致了这个新值上溢出了,大家可以看下图。溢出后,新值在UInt8的承载范围内的那部分是00000000,也就是0

Art/overflowAddition_2x.png

值的下溢出

数值也有可能因为太小而越界。举个例子:

UInt8的最小值是0(二进制为00000000)。使用&-进行溢出减1,就会得到二进制的11111111即十进制的255

Art/overflowUnsignedSubtraction_2x.png

Swift代码是这样的:

var willUnderflow =UInt8.min
// willUnderflow 等于UInt8的最小值0
willUnderflow = willUnderflow &-1// 此时 willUnderflow 等于 255

有符整型也有类似的下溢出,有符整型所有的减法也都是对包括在符号位在内的二进制数进行二进制减法的,这在 “按位左移/右移运算符” 一节提到过。最小的有符整数是-128,即二进制的10000000。用溢出减法减去去1后,变成了01111111,即UInt8所能承载的最大整数127

Art/overflowSignedSubtraction_2x.png

来看看Swift代码:

var signedUnderflow =Int8.min
// signedUnderflow 等于最小的有符整数 -128
signedUnderflow = signedUnderflow &-1// 如今 signedUnderflow 等于 127
除零溢出

一个数除于0 i / 0,或者对0求余数 i % 0,就会产生一个错误。

let x =1
let y = x /0

使用它们对应的可溢出的版本的运算符&/&%进行除0操作时就会得到0值。

let x =1
let y = x &/ 0
// y 等于0

优先级和结合性

运算符的优先级使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。

结合性定义相同优先级的运算符在一起时是怎么组合或关联的,是和左边的一组呢,还是和右边的一组。意思就是,到底是和左边的表达式结合呢,还是和右边的表达式结合?

在混合表达式中,运算符的优先级和结合性是非常重要的。举个例子,为什么下列表达式的结果为4

2+3*4%5// 结果是 4

如果严格地从左计算到右,计算过程会是这样:

  • 2 plus 3 equals 5;
  • 2 + 3 = 5
  • 5 times 4 equals 20;
  • 5 * 4 = 20
  • 20 remainder 5 equals 0
  • 20 / 5 = 4 余 0

但是正确答案是4而不是0。优先级高的运算符要先计算,在Swift和C语言中,都是先乘除后加减的。所以,执行完乘法和求余运算才能执行加减运算。

乘法和求余拥有相同的优先级,在运算过程中,我们还需要结合性,乘法和求余运算都是左结合的。这相当于在表达式中有隐藏的括号让运算从左开始。

2+((3*4)%5)

(3 4) is 12, so this is equivalent to: 3 4 = 12,所以这相当于:

2+(12%5)

(12 % 5) is 2, so this is equivalent to: 12 % 5 = 2,所这又相当于

2+2

计算结果为 4。

查阅Swift运算符的优先级和结合性的完整列表,请看表达式。

注意:

Swift的运算符较C语言和Objective-C来得更简单和保守,这意味着跟基于C的语言可能不一样。所以,在移植已有代码到Swift时,注意去确保代码按你想的那样去执行。

运算符函数

让已有的运算符也可以对自定义的类和结构进行运算,这称为运算符重载。

这个例子展示了如何用+让一个自定义的结构做加法。算术运算符+是一个两目运算符,因为它有两个操作数,而且它必须出现在两个操作数之间。

例子中定义了一个名为Vector2D的二维坐标向量 (x,y) 的结构,然后定义了让两个Vector2D的对象相加的运算符函数。

structVector2D{var x =0.0, y =0.0}@infix func +(left:Vector2D, right:Vector2D)->Vector2D{returnVector2D(x: left.x + right.x, y: left.y + right.y)}

该运算符函数定义了一个全局的+函数,这个函数需要两个Vector2D类型的参数,返回值也是Vector2D类型。需要定义和实现一个中置运算的时候,在关键字func之前写上属性 @infix 就可以了。

在这个代码实现中,参数被命名为了leftright,代表+左边和右边的两个Vector2D对象。函数返回了一个新的Vector2D的对象,这个对象的xy分别等于两个参数对象的xy的和。

这个函数是全局的,而不是Vector2D结构的成员方法,所以任意两个Vector2D对象都可以使用这个中置运算符。

let vector =Vector2D(x:3.0, y:1.0)
let anotherVector =Vector2D(x:2.0, y:4.0)
let combinedVector = vector + anotherVector
// combinedVector 是一个新的Vector2D, 值为 (5.0, 5.0)

这个例子实现两个向量 (3.0,1.0) 和 (2.0,4.0) 相加,得到向量 (5.0,5.0) 的过程。如下图示:

Art/vectorAddition_2x.png

前置和后置运算符

上个例子演示了一个双目中置运算符的自定义实现,同样我们也可以玩标准单目运算符的实现。单目运算符只有一个操作数,在操作数之前就是前置的,如-a; 在操作数之后就是后置的,如i++

实现一个前置或后置运算符时,在定义该运算符的时候于关键字func之前标注 @prefix 或 @postfix属性。

@prefix func -(vector:Vector2D)->Vector2D{returnVector2D(x:-vector.x, y:-vector.y)}

这段代码为Vector2D类型提供了单目减运算-a@prefix属性表明这是个前置运算符。

对于数值,单目减运算符可以把正数变负数,把负数变正数。对于Vector2D,单目减运算将其xy都进进行单目减运算。

let positive =Vector2D(x:3.0, y:4.0)
let negative =-positive
// negative 为 (-3.0, -4.0)
let alsoPositive =-negative
// alsoPositive 为 (3.0, 4.0)
组合赋值运算符

组合赋值是其他运算符和赋值运算符一起执行的运算。如+=把加运算和赋值运算组合成一个操作。实现一个组合赋值符号需要使用@assignment属性,还需要把运算符的左参数设置成inout,因为这个参数会在运算符函数内直接修改它的值。

@assignment func +=(inout left:Vector2D, right:Vector2D){
    left = left + right
}

因为加法运算在之前定义过了,这里无需重新定义。所以,加赋运算符函数使用已经存在的高级加法运算符函数来执行左值加右值的运算。

var original =Vector2D(x:1.0, y:2.0)
let vectorToAdd =Vector2D(x:3.0, y:4.0)
original += vectorToAdd
// original 现在为 (4.0, 6.0)

你可以将 @assignment 属性和 @prefix 或 @postfix 属性起来组合,实现一个Vector2D的前置运算符。

@prefix@assignment func ++(inout vector:Vector2D)->Vector2D{
    vector +=Vector2D(x:1.0, y:1.0)return vector
}

这个前置使用了已经定义好的高级加赋运算,将自己加上一个值为 (1.0,1.0) 的对象然后赋给自己,然后再将自己返回。

var toIncrement =Vector2D(x:3.0, y:4.0)
let afterIncrement =++toIncrement
// toIncrement 现在是 (4.0, 5.0)// afterIncrement 现在也是 (4.0, 5.0)

注意:

默认的赋值符是不可重载的。只有组合赋值符可以重载。三目条件运算符 a?b:c 也是不可重载。

比较运算符

Swift无所知道自定义类型是否相等或不等,因为等于或者不等于由你的代码说了算了。所以自定义的类和结构要使用比较符==!=就需要重载。

定义相等运算符函数跟定义其他中置运算符雷同:

@infix func ==(left:Vector2D, right:Vector2D)->Bool{return(left.x == right.x)&&(left.y == right.y)}@infix func !=(left:Vector2D, right:Vector2D)->Bool{return!(left == right)}

上述代码实现了相等运算符==来判断两个Vector2D对象是否有相等的值,相等的概念就是他们有相同的x值和相同的y值,我们就用这个逻辑来实现。接着使用==的结果实现了不相等运算符!=

现在我们可以使用这两个运算符来判断两个Vector2D对象是否相等。

let twoThree =Vector2D(x:2.0, y:3.0)
let anotherTwoThree =Vector2D(x:2.0, y:3.0)if twoThree == anotherTwoThree {
    println("这两个向量是相等的.")}// prints "这两个向量是相等的."
自定义运算符

标准的运算符不够玩,那你可以声明一些个性的运算符,但个性的运算符只能使用这些字符/ = - + * % < >!& | ^。~

新的运算符声明需在全局域使用operator关键字声明,可以声明为前置,中置或后置的。

operator prefix +++{}

这段代码定义了一个新的前置运算符叫+++,此前Swift并不存在这个运算符。此处为了演示,我们让+++Vector2D对象的操作定义为 双自增 这样一个独有的操作,这个操作使用了之前定义的加赋运算实现了自已加上自己然后返回的运算。

@prefix@assignment func +++(inout vector:Vector2D)->Vector2D{
    vector += vector
    return vector
}

Vector2D 的 +++ 的实现和 ++ 的实现很接近, 唯一不同的前者是加自己, 后者是加值为 (1.0, 1.0) 的向量.

var toBeDoubled =Vector2D(x:1.0, y:4.0)
let afterDoubling =+++toBeDoubled
// toBeDoubled 现在是 (2.0, 8.0)// afterDoubling 现在也是 (2.0, 8.0)
自定义中置运算符的优先级和结合性

可以为自定义的中置运算符指定优先级和结合性。可以回头看看优先级和结合性解释这两个因素是如何影响多种中置运算符混合的表达式的计算的。

结合性(associativity)的值可取的值有leftrightnone。左结合运算符跟其他优先级相同的左结合运算符写在一起时,会跟左边的操作数结合。同理,右结合运算符会跟右边的操作数结合。而非结合运算符不能跟其他相同优先级的运算符写在一起。

结合性(associativity)的值默认为none,优先级(precedence)默认为100

以下例子定义了一个新的中置符+-,是左结合的left,优先级为140

operator infix +-{ associativity left precedence 140}
func +-(left:Vector2D, right:Vector2D)->Vector2D{returnVector2D(x: left.x + right.x, y: left.y - right.y)}
let firstVector =Vector2D(x:1.0, y:2.0)
let secondVector =Vector2D(x:3.0, y:4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector 此时的值为 (4.0, -2.0)

这个运算符把两个向量的x相加,把向量的y相减。因为他实际是属于加减运算,所以让它保持了和加法一样的结合性和优先级(left140)。查阅完整的Swift默认结合性和优先级的设置,请移步表达式;

本文部分原文来自于http://www.swiftguide.cn/翻译小组的译文,共同校对中。

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/advanced-operators/ ‎,商业转载请联系我们~ 感谢您对我们工作的支持~

Swift中文教程(22)泛型

转载自letsswift.com

泛型代码可以让你写出根据自我需求定义、适用于任何类型的,灵活且可重用的函数和类型。它的可以让你避免重复的代码,用一种清晰和抽象的方式来表达代码的意图。

泛型是 Swift 强大特征中的其中一个,许多 Swift 标准库是通过泛型代码构建出来的。事实上,泛型的使用贯穿了整本语言手册,只是你没有发现而已。例如,Swift 的数组和字典类型都是泛型集。你可以创建一个Int数组,也可创建一个String数组,或者甚至于可以是任何其他 Swift 的类型数据数组。同样的,你也可以创建存储任何指定类型的字典(dictionary),而且这些类型可以是没有限制的。

泛型所解决的问题

这里是一个标准的,非泛型函数swapTwoInts,用来交换两个Int值:

func swapTwoInts(inout a:Int, inout b:Int)
    let temporaryA = a
    a = b
    b = temporaryA
}

这个函数使用写入读出(in-out)参数来交换ab的值,请参考[写入读出参数][1]。

swapTwoInts函数可以交换b的原始值到a,也可以交换a的原始值到b,你可以调用这个函数交换两个Int变量值:

var someInt =3var anotherInt =107
swapTwoInts(&someInt,&anotherInt)
println("someInt is now \(someInt), and anotherInt is now \(anotherInt)")// 输出 "someInt is now 107, and anotherInt is now 3"

swapTwoInts函数是非常有用的,但是它只能交换Int值,如果你想要交换两个String或者Double,就不得不写更多的函数,如 swapTwoStringsswapTwoDoublesfunctions,如同如下所示:

func swapTwoStrings(inout a:String, inout b:String){
    let temporaryA = a
    a = b
    b = temporaryA
}

func swapTwoDoubles(inout a:Double, inout b:Double){
    let temporaryA = a
    a = b
    b = temporaryA
}

你可能注意到 swapTwoInts、 swapTwoStringsswapTwoDoubles函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是IntStringDouble

但实际应用中通常需要一个用处更强大并且尽可能的考虑到更多的灵活性单个函数,可以用来交换两个任何类型值,很幸运的是,泛型代码帮你解决了这种问题。(一个这种泛型函数后面已经定义好了。)

注意: 在所有三个函数中,ab的类型是一样的。如果ab不是相同的类型,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个String类型的变量和一个Double类型的变量互相交换值。如果一定要做,Swift 将报编译错误。

泛型函数

泛型函数可以工作于任何类型,这里是一个上面swapTwoInts函数的泛型版本,用于交换两个值:

func swapTwoValues<T>(inout a: T, inout b: T){
    let temporaryA = a
    a = b
    b = temporaryA
}

swapTwoValues函数主体和swapTwoInts函数是一样的,它只在第一行稍微有那么一点点不同于swapTwoInts,如下所示:

func swapTwoInts(inout a:Int, inout b:Int)
func swapTwoValues<T>(inout a: T, inout b: T)

这个函数的泛型版本使用了占位类型名字(通常此情况下用字母T来表示)来代替实际类型名(如InStringDoubl)。占位类型名没有提示T必须是什么类型,但是它提示了ab必须是同一类型T,而不管T表示什么类型。只有swapTwoValues函数在每次调用时所传入的实际类型才能决定T所代表的类型。

另外一个不同之处在于这个泛型函数名后面跟着的展位类型名字(T)是用尖括号括起来的()。这个尖括号告诉 Swift 那个TswapTwoValues函数所定义的一个类型。因为T是一个占位命名类型,Swift 不会去查找命名为T的实际类型。

swapTwoValues函数除了要求传入的两个任何类型值是同一类型外,也可以作为swapTwoInts函数被调用。每次swapTwoValues被调用,T所代表的类型值都会传给函数。

在下面的两个例子中,T分别代表IntString

var someInt =3var anotherInt =107
swapTwoValues(&someInt,&anotherInt)// someInt is now 107, and anotherInt is now 3var someString ="hello"var anotherString ="world"
swapTwoValues(&someString,&anotherString)// someString is now "world", and anotherString is now "hello"

注意 上面定义的函数swapTwoValues是受swap函数启发而实现的。swap函数存在于 Swift 标准库,并可以在其它类中任意使用。如果你在自己代码中需要类似swapTwoValues函数的功能,你可以使用已存在的交换函数swap函数。

类型参数

在上面的swapTwoValues例子中,占位类型T是一种类型参数的示例。类型参数指定并命名为一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(如)。

一旦一个类型参数被指定,那么其可以被使用来定义一个函数的参数类型(如swapTwoValues函数中的参数ab),或作为一个函数返回类型,或用作函数主体中的注释类型。在这种情况下,被类型参数所代表的占位类型不管函数任何时候被调用,都会被实际类型所替换(在上面swapTwoValues例子中,当函数第一次被调用时,TInt替换,第二次调用时,被String替换。)。

你可支持多个类型参数,命名在尖括号中,用逗号分开。

命名类型参数

在简单的情况下,泛型函数或泛型类型需要指定一个占位类型(如上面的swapTwoValues泛型函数,或一个存储单一类型的泛型集,如数组),通常用一单个字母T来命名类型参数。不过,你可以使用任何有效的标识符来作为类型参数名。

如果你使用多个参数定义更复杂的泛型函数或泛型类型,那么使用更多的描述类型参数是非常有用的。例如,Swift 字典(Dictionary)类型有两个类型参数,一个是键,另外一个是值。如果你自己写字典,你或许会定义这两个类型参数为KeyTypeValueType,用来记住它们在你的泛型代码中的作用。

注意 请始终使用大写字母开头的驼峰式命名法(例如TKeyType)来给类型参数命名,以表明它们是类型的占位符,而非类型值。

泛型类型

通常在泛型函数中,Swift 允许你定义你自己的泛型类型。这些自定义类、结构体和枚举作用于任何类型,如同ArrayDictionary的用法。

这部分向你展示如何写一个泛型集类型–Stack(栈)。一个栈是一系列值域的集合,和Array(数组)类似,但其是一个比 Swift 的Array类型更多限制的集合。一个数组可以允许其里面任何位置的插入/删除操作,而栈,只允许在集合的末端添加新的项(如同push一个新值进栈)。同样的一个栈也只能从末端移除项(如同pop一个值出栈)。

注意 栈的概念已被UINavigationController类使用来模拟试图控制器的导航结构。你通过调用UINavigationControllerpushViewController:animated:方法来为导航栈添加(add)新的试图控制器;而通过popViewControllerAnimated:的方法来从导航栈中移除(pop)某个试图控制器。每当你需要一个严格的后进先出方式来管理集合,堆栈都是最实用的模型。

下图展示了一个栈的压栈(push)/出栈(pop)的行为:

![此处输入图片的描述][2]

  1. 现在有三个值在栈中;
  2. 第四个值“pushed”到栈的顶部;
  3. 现在有四个值在栈中,最近的那个在顶部;
  4. 栈中最顶部的那个项被移除,或称之为“popped”;
  5. 移除掉一个值后,现在栈又重新只有三个值。

这里展示了如何写一个非泛型版本的栈,Int值型的栈:

structIntStack{var items =Int[]()
    mutating func push(item:Int){
        items.append(item)}
    mutating func pop()->Int{return items.removeLast()}}

这个结构体在栈中使用一个Array性质的items存储值。Stack提供两个方法:pushpop,从栈中压进一个值和移除一个值。这些方法标记为可变的,因为他们需要修改(或转换)结构体的items数组。

上面所展现的IntStack类型只能用于Int值,不过,其对于定义一个泛型Stack类(可以处理任何类型值的栈)是非常有用的。

这里是一个相同代码的泛型版本:

structStack<T>{var items = T[]()
    mutating func push(item: T){
        items.append(item)}
    mutating func pop()-> T {return items.removeLast()}}

注意到Stack的泛型版本基本上和非泛型版本相同,但是泛型版本的占位类型参数为T代替了实际Int类型。这种类型参数包含在一对尖括号里(<T>),紧随在结构体名字后面。

T定义了一个名为“某种类型T”的节点提供给后来用。这种将来类型可以在结构体的定义里任何地方表示为“T”。在这种情况下,T在如下三个地方被用作节点:

  • 创建一个名为items的属性,使用空的T类型值数组对其进行初始化;
  • 指定一个包含一个参数名为itempush方法,该参数必须是T类型;
  • 指定一个pop方法的返回值,该返回值将是一个T类型值。

当创建一个新单例并初始化时, 通过用一对紧随在类型名后的尖括号里写出实际指定栈用到类型,创建一个Stack实例,同创建ArrayDictionary一样:

var stackOfStrings =Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")// 现在栈已经有4个string了

下图将展示stackOfStrings如何push这四个值进栈的过程:

![此处输入图片的描述][3]

从栈中pop并移除值”cuatro”:

let fromTheTop = stackOfStrings.pop()// fromTheTop is equal to "cuatro", and the stack now contains 3 strings

下图展示了如何从栈中pop一个值的过程: ![此处输入图片的描述][4]

由于Stack是泛型类型,所以在 Swift 中其可以用来创建任何有效类型的栈,这种方式如同ArrayDictionary

类型约束

swapTwoValues函数和Stack类型可以作用于任何类型,不过,有的时候对使用在泛型函数和泛型类型上的类型强制约束为某种特定类型是非常有用的。类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。

例如,Swift 的Dictionary类型对作用于其键的类型做了些限制。在[字典][5]的描述中,字典的键类型必须是可哈希,也就是说,必须有一种方法可以使其是唯一的表示。Dictionary之所以需要其键是可哈希是为了以便于其检查其是否包含某个特定键的值。如无此需求,Dictionary即不会告诉是否插入或者替换了某个特定键的值,也不能查找到已经存储在字典里面的给定键值。

这个需求强制加上一个类型约束作用于Dictionary的键上,当然其键类型必须遵循Hashable协议(Swift 标准库中定义的一个特定协议)。所有的 Swift 基本类型(如StringInt, DoubleBool)默认都是可哈希。

当你创建自定义泛型类型时,你可以定义你自己的类型约束,当然,这些约束要支持泛型编程的强力特征中的多数。抽象概念如可哈希具有的类型特征是根据他们概念特征来界定的,而不是他们的直接类型特征。

类型约束语法

你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同):

func someFunction<T:SomeClass, U:SomeProtocol>(someT: T, someU: U){// function body goes here}

上面这个假定函数有两个类型参数。第一个类型参数T,有一个需要T必须是SomeClass子类的类型约束;第二个类型参数U,有一个需要U必须遵循SomeProtocol协议的类型约束。

类型约束行为

这里有个名为findStringIndex的非泛型函数,该函数功能是去查找包含一给定String值的数组。若查找到匹配的字符串,findStringIndex函数返回该字符串在数组中的索引值(Int),反之则返回nil

func findStringIndex(array:String[], valueToFind:String)->Int?{for(index, value)in enumerate(array){if value == valueToFind {return index
        }}returnnil}

findStringIndex函数可以作用于查找一字符串数组中的某个字符串:

let strings =["cat","dog","llama","parakeet","terrapin"]if let foundIndex = findStringIndex(strings,"llama"){
    println("The index of llama is \(foundIndex)")}// 输出 "The index of llama is 2"

如果只是针对字符串而言查找在数组中的某个值的索引,用处不是很大,不过,你可以写出相同功能的泛型函数findIndex,用某个类型T值替换掉提到的字符串。

这里展示如何写一个你或许期望的findStringIndex的泛型版本findIndex。请注意这个函数仍然返回Int,是不是有点迷惑呢,而不是泛型类型?那是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数不会编译,原因在例子后面会说明:

func findIndex<T>(array: T[], valueToFind: T)->Int?{for(index, value)in enumerate(array){if value == valueToFind {return index
        }}returnnil}

上面所写的函数不会编译。这个问题的位置在等式的检查上,“if value == valueToFind”。不是所有的 Swift 中的类型都可以用等式符(==)进行比较。例如,如果你创建一个你自己的类或结构体来表示一个复杂的数据模型,那么 Swift 没法猜到对于这个类或结构体而言“等于”的意思。正因如此,这部分代码不能可能保证工作于每个可能的类型T,当你试图编译这部分代码时估计会出现相应的错误。

不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个Equatable协议,该协议要求任何遵循的类型实现等式符(==)和不等符(!=)对任何两个该类型进行比较。所有的 Swift 标准类型自动支持Equatable协议。

任何Equatable类型都可以安全的使用在findIndex函数中,因为其保证支持等式操作。为了说明这个事实,当你定义一个函数时,你可以写一个Equatable类型约束作为类型参数定义的一部分:

func findIndex<T:Equatable>(array: T[], valueToFind: T)->Int?{for(index, value)in enumerate(array){if value == valueToFind {return index
        }}returnnil}

findIndex中这个单个类型参数写做:T: Equatable,也就意味着“任何T类型都遵循Equatable协议”。

findIndex函数现在则可以成功的编译过,并且作用于任何遵循Equatable的类型,如DoubleString:

let doubleIndex = findIndex([3.14159,0.1,0.25],9.3)// doubleIndex is an optional Int with no value, because 9.3 is not in the array
let stringIndex = findIndex(["Mike","Malcolm","Andrea"],"Andrea")// stringIndex is an optional Int containing a value of 2

关联类型

当定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分是非常有用的。一个关联类型给定作用于协议部分的类型一个节点名(或别名)。作用于关联类型上实际类型是不需要指定的,直到该协议接受。关联类型被指定为typealias关键字。

关联类型行为

这里是一个Container协议的例子,定义了一个ItemType关联类型:

protocol Container{
    typealias ItemType
    mutating func append(item:ItemType)var count:Int{get}
    subscript(i:Int)->ItemType{get}}

Container协议定义了三个任何容器必须支持的兼容要求:

  • 必须可能通过append方法添加一个新item到容器里;
  • 必须可能通过使用count属性获取容器里items的数量,并返回一个Int值;
  • 必须可能通过容器的Int索引值下标可以检索到每一个item。

这个协议没有指定容器里item是如何存储的或何种类型是允许的。这个协议只指定三个任何遵循Container类型所必须支持的功能点。一个遵循的类型也可以提供其他额外的功能,只要满足这三个条件。

任何遵循Container协议的类型必须指定存储在其里面的值类型,必须保证只有正确类型的items可以加进容器里,必须明确可以通过其下标返回item类型。

为了定义这三个条件,Container协议需要一个方法指定容器里的元素将会保留,而不需要知道特定容器的类型。Container协议需要指定任何通过append方法添加到容器里的值和容器里元素是相同类型,并且通过容器下标返回的容器元素类型的值的类型是相同类型。

为了达到此目的,Container协议声明了一个ItemType的关联类型,写作typealias ItemType。The protocol does not define what ItemType is an alias for—that information is left for any conforming type to provide(这个协议不会定义ItemType是遵循类型所提供的何种信息的别名)。尽管如此,ItemType别名支持一种方法识别在一个容器里的items类型,以及定义一种使用在append方法和下标中的类型,以便保证任何期望的Container的行为是强制性的。

这里是一个早前IntStack类型的非泛型版本,适用于遵循Container协议:

structIntStack:Container{// original IntStack implementationvar items =Int[]()
    mutating func push(item:Int){
        items.append(item)}
    mutating func pop()->Int{return items.removeLast()}// conformance to the Container protocol
    typealias ItemType=Int
    mutating func append(item:Int){self.push(item)}var count:Int{return items.count
    }
    subscript(i:Int)->Int{return items[i]}}

IntStack类型实现了Container协议的所有三个要求,在IntStack类型的每个包含部分的功能都满足这些要求。

此外,IntStack指定了Container的实现,适用的ItemType被用作Int类型。对于这个Container协议实现而言,定义 typealias ItemType = Int,将抽象的ItemType类型转换为具体的Int类型。

感谢Swift类型参考,你不用在IntStack定义部分声明一个具体的IntItemType。由于IntStack遵循Container协议的所有要求,只要通过简单的查找append方法的item参数类型和下标返回的类型,Swift就可以推断出合适的ItemType来使用。确实,如果上面的代码中你删除了 typealias ItemType = Int这一行,一切仍旧可以工作,因为它清楚的知道ItemType使用的是何种类型。

你也可以生成遵循Container协议的泛型Stack类型:

structStack<T>:Container{// original Stack<T> implementationvar items = T[]()
    mutating func push(item: T){
        items.append(item)}
    mutating func pop()-> T {return items.removeLast()}// conformance to the Container protocol
    mutating func append(item: T){self.push(item)}var count:Int{return items.count
    }
    subscript(i:Int)-> T {return items[i]}}

这个时候,占位类型参数T被用作append方法的item参数和下标的返回类型。Swift 因此可以推断出被用作这个特定容器的ItemTypeT的合适类型。

扩展一个存在的类型为一指定关联类型

在[使用扩展来添加协议兼容性][6]中有描述扩展一个存在的类型添加遵循一个协议。这个类型包含一个关联类型的协议。

Swift的Array已经提供append方法,一个count属性和通过下标来查找一个自己的元素。这三个功能都达到Container协议的要求。也就意味着你可以扩展Array去遵循Container协议,只要通过简单声明Array适用于该协议而已。如何实践这样一个空扩展,在[使用扩展来声明协议的采纳][7]中有描述这样一个实现一个空扩展的行为:

extension Array:Container{}

如同上面的泛型Stack类型一样,Array的append方法和下标保证Swift可以推断出ItemType所使用的适用的类型。定义了这个扩展后,你可以将任何Array当作Container来使用。

Where 语句

[类型约束][8]中描述的类型约束确保你定义关于类型参数的需求和一泛型函数或类型有关联。

对于关联类型的定义需求也是非常有用的。你可以通过这样去定义where语句作为一个类型参数队列的一部分。一个where语句使你能够要求一个关联类型遵循一个特定的协议,以及(或)那个特定的类型参数和关联类型可以是相同的。你可写一个where语句,通过紧随放置where关键字在类型参数队列后面,其后跟着一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型的等于关系。

下面的列子定义了一个名为allItemsMatch的泛型函数,用来检查是否两个Container单例包含具有相同顺序的相同元素。如果匹配到所有的元素,那么返回一个为trueBoolean值,反之,则相反。

这两个容器可以被检查出是否是相同类型的容器(虽然它们可以是),但他们确实拥有相同类型的元素。这个需求通过一个类型约束和where语句结合来表示:

func allItemsMatch<
    C1:Container, C2:Containerwhere C1.ItemType== C2.ItemType, C1.ItemType:Equatable>(someContainer: C1, anotherContainer: C2)->Bool{// check that both containers contain the same number of itemsif someContainer.count != anotherContainer.count {returnfalse}// check each pair of items to see if they are equivalentfor i in0..someContainer.count {if someContainer[i]!= anotherContainer[i]{returnfalse}}// all items match, so return truereturntrue}

这个函数用了两个参数:someContaineranotherContainersomeContainer参数是类型C1anotherContainer参数是类型C2C1C2是容器的两个占位类型参数,决定了这个函数何时被调用。

这个函数的类型参数列紧随在两个类型参数需求的后面:

  • C1必须遵循Container协议 (写作 C1: Container)。
  • C2必须遵循Container协议 (写作 C2: Container)。
  • C1ItemType同样是C2的ItemType(写作 C1.ItemType == C2.ItemType)。
  • C1ItemType必须遵循Equatable协议 (写作 C1.ItemType: Equatable)。

第三个和第四个要求被定义为一个where语句的一部分,写在关键字where后面,作为函数类型参数链的一部分。

这些要求意思是:

someContainer是一个C1类型的容器。 anotherContainer是一个C2类型的容器。 someContaineranotherContainer包含相同的元素类型。 someContainer中的元素可以通过不等于操作(!=)来检查它们是否彼此不同。

第三个和第四个要求结合起来的意思是anotherContainer中的元素也可以通过 != 操作来检查,因为他们在someContainer中元素确实是相同的类型。

这些要求能够使allItemsMatch函数比较两个容器,即便他们是不同的容器类型。

allItemsMatch首先检查两个容器是否拥有同样数目的items,如果他们的元素数目不同,没有办法进行匹配,函数就会false

检查完之后,函数通过for-in循环和半闭区间操作(..)来迭代someContainer中的所有元素。对于每个元素,函数检查是否someContainer中的元素不等于对应的anotherContainer中的元素,如果这两个元素不等,则这两个容器不匹配,返回false

如果循环体结束后未发现没有任何的不匹配,那表明两个容器匹配,函数返回true

这里演示了allItemsMatch函数运算的过程:

var stackOfStrings =Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")var arrayOfStrings =["uno","dos","tres"]if allItemsMatch(stackOfStrings, arrayOfStrings){
    println("All items match.")}else{
    println("Not all items match.")}// 输出 "All items match."

上面的例子创建一个Stack单例来存储String,然后压了三个字符串进栈。这个例子也创建了一个Array单例,并初始化包含三个同栈里一样的原始字符串。即便栈和数组否是不同的类型,但他们都遵循Container协议,而且他们都包含同样的类型值。你因此可以调用allItemsMatch函数,用这两个容器作为它的参数。在上面的例子中,allItemsMatch函数正确的显示了所有的这两个容器的items匹配。

本文部分原文来自于http://www.swiftguide.cn/翻译小组的译文,共同校对中。

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/generics/,商业转载请联系我们~ 感谢您对我们工作的支持~

Swift中文教程(21)协议

转载自letsswift.com

Protocol(协议)用于统一方法和属性的名称,而不实现任何功能。协议能够被类,枚举,结构体实现,满足协议要求的类,枚举,结构体被称为协议的遵循者

遵循者需要提供协议指定的成员,如属性,方法,操作符,下标等。

协议的语法

协议的定义与类,结构体,枚举的定义非常相似,如下所示:

protocol SomeProtocol{// 协议内容}

在类,结构体,枚举的名称后加上协议名称,中间以冒号:分隔即可实现协议;实现多个协议时,各协议之间用逗号,分隔,如下所示:

structSomeStructure:FirstProtocol,AnotherProtocol{// 结构体内容}

当某个类含有父类的同时并实现了协议,应当把父类放在所有的协议之前,如下所示:

classSomeClass:SomeSuperClass,FirstProtocol,AnotherProtocol{// 类的内容}

属性要求

协议能够要求其遵循者必须含有一些特定名称和类型实例属性(instance property)类属性 (type property),也能够要求属性的(设置权限)settable 和(访问权限)gettable,但它不要求属性存储型属性(stored property)还是计算型属性(calculate property)

通常前置var关键字将属性声明为变量。在属性声明后写上{ get set }表示属性为可读写的。{ get }用来表示属性为可读的。即使你为可读的属性实现了setter方法,它也不会出错。

protocol SomeProtocol{var musBeSettable :Int{getset}var doesNotNeedToBeSettable:Int{get}}

用类来实现协议时,使用class关键字来表示该属性为类成员;用结构体或枚举实现协议时,则使用static关键字来表示:

protocol AnotherProtocol{classvar someTypeProperty:Int{getset}}

protocol FullyNamed{var fullName:String{get}}

FullyNamed协议含有fullName属性。因此其遵循者必须含有一个名为fullName,类型为String的可读属性。

structPerson:FullyNamed{var fullName:String}
let john =Person(fullName:"John Appleseed")//john.fullName 为 "John Appleseed"

Person结构体含有一个名为fullName存储型属性,完整的遵循了协议。(若协议未被完整遵循,编译时则会报错)。

如下所示,Startship遵循FullyNamed协议:

classStarship:FullyNamed{var prefix:String?var name:String
    init(name:String, prefix:String?=nil){self.anme = name
        self.prefix = prefix
    }var fullName:String{return(prefix ? prefix !+" ":" ")+ name
    }}var ncc1701 =Starship(name:"Enterprise", prefix:"USS")// ncc1701.fullName == "USS Enterprise"

Starship类将fullName实现为可读的计算型属性。它的每一个实例都有一个名为name的必备属性和一个名为prefix的可选属性。 当prefix存在时,将prefix插入到name之前来为Starship构建fullName

方法要求

协议能够要求其遵循者必备某些特定的实例方法类方法。协议方法的声明与普通方法声明相似,但它不需要方法内容。

注意:

协议方法支持变长参数(variadic parameter),不支持默认参数(default parameter)

前置class关键字表示协议中的成员为类成员;当协议用于被枚举结构体遵循时,则使用static关键字。如下所示:

protocol SomeProtocol{class func someTypeMethod()}

protocol RandomNumberGenerator{
    func random()->Double}

RandomNumberGenerator协议要求其遵循者必须拥有一个名为random, 返回值类型为Double的实例方法。(我们假设随机数在[0,1]区间内)。

LinearCongruentialGenerator遵循RandomNumberGenerator协议,并提供了一个叫做线性同余生成器(linear congruential generator)的伪随机数算法。

classLinearCongruentialGenerator:RandomNumberGenerator{var lastRandom =42.0
    let m =139968.0
    let a =3877.0
    let c =29573.0
    func random()->Double{
        lastRandom =((lastRandom * a + c)% m)return lastRandom / m
    }}
let generator =LinearCongruentialGenerator()
println("Here's a random number: \(generator.random())")// 输出 : "Here's a random number: 0.37464991998171"
println("And another one: \(generator.random())")// 输出 : "And another one: 0.729023776863283"

突变方法要求

能在方法函数内部改变实例类型的方法称为突变方法。在值类型(Value Type)(译者注:特指结构体和枚举)中的的函数前缀加上mutating关键字来表示该函数允许改变该实例和其属性的类型。 这一变换过程在实例方法(Instance Methods)章节中有详细描述。

(译者注:类中的成员为引用类型(Reference Type),可以方便的修改实例及其属性的值而无需改变类型;而结构体枚举中的成员均为值类型(Value Type),修改变量的值就相当于修改变量的类型,而Swift默认不允许修改类型,因此需要前置mutating关键字用来表示该函数中能够修改类型)

注意:

class实现协议中的mutating方法时,不用写mutating关键字;用结构体枚举实现协议中的mutating方法时,必须写mutating关键字。

如下所示,Togglable协议含有toggle函数。根据函数名称推测,toggle可能用于切换或恢复某个属性的状态。mutating关键字表示它为突变方法

protocol Togglable{
    mutating func toggle()}

当使用枚举结构体来实现Togglabl协议时,必须在toggle方法前加上mutating关键字。

如下所示,OnOffSwitch枚举遵循Togglable协议,OnOff两个成员用于表示当前状态

enumOnOffSwitch:Togglable{caseOff,On
    mutating func toggle(){switchself{caseOff:self=OncaseOn:self=Off}}}var lightSwitch =OnOffSwitch.Off
lightSwitch.toggle()//lightSwitch 现在的值为 .On

协议类型

协议本身不实现任何功能,但你可以将它当做类型来使用。

使用场景:

  • 作为函数,方法或构造器中的参数类型,返回值类型
  • 作为常量,变量,属性的类型
  • 作为数组,字典或其他容器中的元素类型

注意:

协议类型应与其他类型(Int,Double,String)的写法相同,使用驼峰式

classDice{
    let sides:Int
    let generator:RandomNumberGenerator
    init(sides:Int, generator:RandomNumberGenerator){self.sides = sides
        self.generator = generator
    }
    func roll()->Int{returnInt(generator.random()*Double(sides))+1}}

这里定义了一个名为 Dice的类,用来代表桌游中的N个面的骰子。

Dice含有sidesgenerator两个属性,前者用来表示骰子有几个面,后者为骰子提供一个随机数生成器。由于后者为RandomNumberGenerator的协议类型。所以它能够被赋值为任意遵循该协议的类型。

此外,使用构造器(init)来代替之前版本中的setup操作。构造器中含有一个名为generator,类型为RandomNumberGenerator的形参,使得它可以接收任意遵循RandomNumberGenerator协议的类型。

roll方法用来模拟骰子的面值。它先使用generatorrandom方法来创建一个[0-1]区间内的随机数种子,然后加工这个随机数种子生成骰子的面值。

如下所示,LinearCongruentialGenerator的实例作为随机数生成器传入Dice构造器

var d6 =Dice(sides:6,generator:LinearCongruentialGenerator())for _ in1...5{
    println("Random dice roll is \(d6.roll())")}//输出结果//Random dice roll is 3//Random dice roll is 5//Random dice roll is 4//Random dice roll is 5//Random dice roll is 4

委托(代理)模式

委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能交由(委托)给其他的类型。

委托模式的实现很简单: 定义协议封装那些需要被委托的函数和方法, 使其遵循者拥有这些被委托的函数和方法

委托模式可以用来响应特定的动作或接收外部数据源提供的数据,而无需要知道外部数据源的类型。

下文是两个基于骰子游戏的协议:

protocol DiceGame{var dice:Dice{get}
    func play()}
protocol DiceGameDelegate{
    func gameDidStart(game:DiceGame)
    func game(game:DiceGame, didStartNewTurnWithDiceRoll diceRoll:Int)
    func gameDidEnd(game:DiceGame)}

DiceGame协议可以在任意含有骰子的游戏中实现,DiceGameDelegate协议可以用来追踪DiceGame的游戏过程。

如下所示,SnakesAndLaddersSnakes and Ladders(译者注:控制流章节有该游戏的详细介绍)游戏的新版本。新版本使用Dice作为骰子,并且实现了DiceGameDiceGameDelegate协议

classSnakesAndLadders:DiceGame{
    let finalSquare =25
    let dic =Dice(sides:6, generator:LinearCongruentialGenerator())var square =0var board:Int[]
    init(){
        board =Int[](count: finalSquare +1, repeatedValue:0)
        board[03]=+08; board[06]=+11; borad[09]=+09; board[10]=+02
        borad[14]=-10; board[19]=-11; borad[22]=-02; board[24]=-08}vardelegate:DiceGameDelegate?
     func play(){
         square =0delegate?.gameDidStart(self)
         gameLoop:while square != finalSquare {
             let diceRoll = dice.roll()delegate?.game(self,didStartNewTurnWithDiceRoll: diceRoll)switch square + diceRoll {case finalSquare:break gameLoop
             case let newSquare where newSquare > finalSquare:continue gameLoop
             default:
             square += diceRoll
             square += board[square]}}delegate?.gameDIdEnd(self)}}

游戏的初始化设置(setup)SnakesAndLadders类的构造器(initializer)实现。所有的游戏逻辑被转移到了play方法中。

注意:

因为delegate并不是该游戏的必备条件,delegate被定义为遵循DiceGameDelegate协议的可选属性

DicegameDelegate协议提供了三个方法用来追踪游戏过程。被放置于游戏的逻辑中,即play()方法内。分别在游戏开始时,新一轮开始时,游戏结束时被调用。

因为delegate是一个遵循DiceGameDelegate的可选属性,因此在play()方法中使用了可选链来调用委托方法。 若delegate属性为nil, 则委托调用优雅地失效。若delegate不为nil,则委托方法被调用

如下所示,DiceGameTracker遵循了DiceGameDelegate协议

classDiceGameTracker:DiceGameDelegate{var numberOfTurns =0
    func gameDidStart(game:DiceGame){
        numberOfTurns =0if game isSnakesAndLadders{
            println("Started a new game of Snakes and Ladders")}
        println("The game is using a \(game.dice.sides)-sided dice")}
    func game(game:DiceGame, didStartNewTurnWithDiceRoll diceRoll:Int){++numberOfTurns
        println("Rolled a \(diceRoll)")}
    func gameDidEnd(game:DiceGame){
        println("The game lasted for \(numberOfTurns) turns")}}

DiceGameTracker实现了DiceGameDelegate协议的方法要求,用来记录游戏已经进行的轮数。 当游戏开始时,numberOfTurns属性被赋值为0;在每新一轮中递加;游戏结束后,输出打印游戏的总轮数。

gameDidStart方法从game参数获取游戏信息并输出。game在方法中被当做DiceGame类型而不是SnakeAndLadders类型,所以方法中只能访问DiceGame协议中的成员。

DiceGameTracker的运行情况,如下所示:

let tracker =DiceGameTracker()
let game =SnakesAndLadders()
game.delegate= tracker
game.play()// Started a new game of Snakes and Ladders// The game is using a 6-sided dice// Rolled a 3// Rolled a 5// Rolled a 4// Rolled a 5// The game lasted for 4 turns”

在扩展中添加协议成员

即便无法修改源代码,依然可以通过扩展(Extension)来扩充已存在类型(译者注: 类,结构体,枚举等)。扩展可以为已存在的类型添加属性方法下标协议等成员。详情请在扩展章节中查看。

注意:

通过扩展为已存在的类型遵循协议时,该类型的所有实例也会随之添加协议中的方法

TextRepresentable协议含有一个asText,如下所示:

protocol TextRepresentable{
    func asText()->String}

通过扩展为上一节中提到的Dice类遵循TextRepresentable协议

extension Dice:TextRepresentable{
    cun asText()->String{return"A \(sides)-sided dice"}}

从现在起,Dice类型的实例可被当作TextRepresentable类型:

let d12 =Dice(sides:12,generator:LinearCongruentialGenerator())
println(d12.asText())// 输出 "A 12-sided dice"

SnakesAndLadders类也可以通过扩展的方式来遵循协议:

extension SnakeAndLadders:TextRepresentable{
    func asText()->String{return"A game of Snakes and Ladders with \(finalSquare) squares"}}
println(game.asText())// 输出 "A game of Snakes and Ladders with 25 squares"

通过延展补充协议声明

当一个类型已经实现了协议中的所有要求,却没有声明时,可以通过扩展来补充协议声明:

structHamster{var name:String
    func asText()->String{return"A hamster named \(name)"}}
extension Hamster:TextRepresentabl{}

从现在起,Hamster的实例可以作为TextRepresentable类型使用

let simonTheHamster =Hamster(name:"Simon")
let somethingTextRepresentable:TextRepresentabl= simonTheHamester
println(somethingTextRepresentable.asText())// 输出 "A hamster named Simon"

注意:

即时满足了协议的所有要求,类型也不会自动转变,因此你必须为它做出明显的协议声明

集合中的协议类型

协议类型可以被集合使用,表示集合中的元素均为协议类型:

let things:TextRepresentable[]=[game,d12,simoTheHamster]

如下所示,things数组可以被直接遍历,并调用其中元素的asText()函数:

for thing in things {
    println(thing.asText())}// A game of Snakes and Ladders with 25 squares// A 12-sided dice// A hamster named Simon

thing被当做是TextRepresentable类型而不是DiceDiceGameHamster等类型。因此能且仅能调用asText方法

协议的继承

协议能够继承一到多个其他协议。语法与类的继承相似,多个协议间用逗号,分隔

protocol InheritingProtocol:SomeProtocol,AnotherProtocol{// 协议定义}

如下所示,PrettyTextRepresentable协议继承了TextRepresentable协议

protocol PrettyTextRepresentable:TextRepresentable{
    func asPrettyText()->String}

遵循``PrettyTextRepresentable协议的同时,也需要遵循TextRepresentable`协议。

如下所示,用扩展SnakesAndLadders遵循PrettyTextRepresentable协议:

extension SnakesAndLadders:PrettyTextRepresentable{
    func asPrettyText()->String{var output = asText()+":\n"for index in1...finalSquare {switch board[index]{case let ladder where ladder >0:
                output +="▲ "case let snake where snake <0:
                output +="▼ "default:
                output +="○ "}}return output
    }}

for in中迭代出了board数组中的每一个元素:

  • 当从数组中迭代出的元素的值大于0时,用表示
  • 当从数组中迭代出的元素的值小于0时,用表示
  • 当从数组中迭代出的元素的值等于0时,用表示

任意SankesAndLadders的实例都可以使用asPrettyText()方法。

println(game.asPrettyText())// A game of Snakes and Ladders with 25 squares:// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○

协议合成

一个协议可由多个协议采用protocol<SomeProtocol, AnotherProtocol>这样的格式进行组合,称为协议合成(protocol composition)

举个例子:

protocol Named{var name:String{get}}
protocol Aged{var age:Int{get}}structPerson:Named,Aged{var name:Stringvar age:Int}
func wishHappyBirthday(celebrator: protocol<Named,Aged>){
    println("Happy birthday \(celebrator.name) - you're \(celebrator.age)!")}
let birthdayPerson =Person(name:"Malcolm", age:21)
wishHappyBirthday(birthdayPerson)// 输出 "Happy birthday Malcolm - you're 21!

Named协议包含String类型的name属性;Aged协议包含Int类型的age属性。Person结构体遵循了这两个协议。

wishHappyBirthday函数的形参celebrator的类型为protocol<Named,Aged>。可以传入任意遵循这两个协议的类型的实例

注意:

协议合成并不会生成一个新协议类型,而是将多个协议合成为一个临时的协议,超出范围后立即失效。

检验协议的一致性

使用is检验协议一致性,使用as将协议类型向下转换(downcast)为的其他协议类型。检验与转换的语法和之前相同(详情查看类型检查):

  • is操作符用来检查实例是否遵循了某个协议
  • as?返回一个可选值,当实例遵循协议时,返回该协议类型;否则返回nil
  • as用以强制向下转换型。
@objc protocol HasArea{var area:Double{get}}

注意:

@objc用来表示协议是可选的,也可以用来表示暴露给Objective-C的代码,此外,@objc型协议只对有效,因此只能在中检查协议的一致性。详情查看Using Siwft with Cocoa and Objectivei-c

classCircle:HasArea{
    let pi =3.1415927var radius:Doublevar area:≈radius }
    init(radius:Double){self.radius = radius }}classCountry:HasArea{var area:Double
    init(area:Double){self.area = area }}

CircleCountry都遵循了HasArea协议,前者把area写为计算型属性(computed property),后者则把area写为存储型属性(stored property)。

如下所示,Animal类没有实现任何协议

classAnimal{var legs:Int
    init(legs:Int){self.legs = legs }}

Circle,Country,Animal并没有一个相同的基类,所以采用AnyObject类型的数组来装载在他们的实例,如下所示:

let objects:AnyObject[]=[Circle(radius:2.0),Country(area:243_610),Animal(legs:4)]

如下所示,在迭代时检查object数组的元素是否遵循HasArea协议:

forobjectin objects {if let objectWithArea =objectas?HasArea{
        println("Area is \(objectWithArea.area)")}else{
        println("Something that doesn't have an area")}}// Area is 12.5663708// Area is 243610.0// Something that doesn't have an area

当数组中的元素遵循HasArea协议时,通过as?操作符将其可选绑定(optional binding)objectWithArea常量上。

objects数组中元素的类型并不会因为向下转型而改变,当它们被赋值给objectWithArea时只被视为HasArea类型,因此只有area属性能够被访问。

可选协议要求

可选协议含有可选成员,其遵循者可以选择是否实现这些成员。在协议中使用@optional关键字作为前缀来定义可选成员。

可选协议在调用时使用可选链,详细内容在可选链章节中查看。

someOptionalMethod?(someArgument)一样,你可以在可选方法名称后加上?来检查该方法是否被实现。可选方法可选属性都会返回一个可选值(optional value),当其不可访问时,?之后语句不会执行,并返回nil

注意:

可选协议只能在含有@objc前缀的协议中生效。且@objc的协议只能被遵循。

Counter类使用CounterDataSource类型的外部数据源来提供增量值(increment amount),如下所示:

@objc protocol CounterDataSource{@optional func incrementForCount(count:Int)->Int@optionalvar fixedIncrement:Int{get}}

CounterDataSource含有incrementForCount可选方法fiexdIncrement可选属性

注意:

CounterDataSource中的属性和方法都是可选的,因此可以在类中声明但不实现这些成员,尽管技术上允许这样做,不过最好不要这样写。

Counter类含有CounterDataSource?类型的可选属性dataSource,如下所示:

@objcclassCounter{var count =0var dataSource:CounterDataSource?
    func increment(){if let amount = dataSource?.incrementForCount?(count){
            count += amount
        }elseif let amount = dataSource?.fixedIncrement?{
            count += amount
        }}}

count属性用于存储当前的值,increment方法用来为count赋值。

increment方法通过可选链,尝试从两种可选成员中获取count

  1. 由于dataSource可能为nil,因此在dataSource后边加上了?标记来表明只在dataSource非空时才去调用incrementForCount`方法。
  2. 即使dataSource存在,但是也无法保证其是否实现了incrementForCount方法,因此在incrementForCount方法后边也加有?标记。

在调用incrementForCount方法后,Int可选值通过可选绑定(optional binding)自动拆包并赋值给常量amount

incrementForCount不能被调用时,尝试使用可选属性``fixedIncrement来代替。

ThreeSource实现了CounterDataSource协议,如下所示:

classThreeSource:CounterDataSource{
    let fixedIncrement =3}

使用ThreeSource作为数据源开实例化一个Counter

var counter =Counter()
counter.dataSource =ThreeSource()for _ in1...4{
    counter.increment()
    println(counter.count)}// 3// 6// 9// 12

TowardsZeroSource实现了CounterDataSource协议中的incrementForCount方法,如下所示:

classTowardsZeroSource:CounterDataSource{
func incrementForCount(count:Int)->Int{if count ==0{return0}elseif count <0{return1}else{return-1}}}

下边是执行的代码:

counter.count =-4
counter.dataSource =TowardsZeroSource()for _ in1...5{
    counter.increment()
    println(counter.count)}// -3// -2// -1// 0// 0

本文部分原文来自于http://www.swiftguide.cn/翻译小组的译文,共同校对中。

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/protocols/,商业转载请联系我们~ 感谢您对我们工作的支持~

Swift中文教程(20)扩展

转载自letsswift.com

扩展就是向一个已有的类、结构体或枚举类型添加新功能(functionality)。这包括在没有权限获取原始源代码的情况下扩展类型的能力(即逆向建模)。扩展和 Objective-C 中的分类(categories)类似。(不过与Objective-C不同的是,Swift 的扩展没有名字。)

Swift 中的扩展可以:

  • 添加计算型属性和计算静态属性
  • 定义实例方法和类型方法
  • 提供新的构造器
  • 定义下标
  • 定义和使用新的嵌套类型
  • 使一个已有类型符合某个接口

注意:

如果你定义了一个扩展向一个已有类型添加新功能,那么这个新功能对该类型的所有已有实例中都是可用的,即使它们是在你的这个扩展的前面定义的。

扩展语法(Extension Syntax)

声明一个扩展使用关键字extension

extension SomeType{// 加到SomeType的新功能写到这里}

一个扩展可以扩展一个已有类型,使其能够适配一个或多个协议(protocol)。当这种情况发生时,接口的名字应该完全按照类或结构体的名字的方式进行书写:

extension SomeType:SomeProtocol,AnotherProctocol{// 协议实现写到这里}

按照这种方式添加的协议遵循者(protocol conformance)被称之为在扩展中添加协议遵循者

计算型属性(Computed Properties)

扩展可以向已有类型添加计算型实例属性和计算型类型属性。下面的例子向 Swift 的内建Double类型添加了5个计算型实例属性,从而提供与距离单位协作的基本支持。

extension Double{var km:Double{returnself*1_000.0}var m :Double{returnself}var cm:Double{returnself/100.0}var mm:Double{returnself/1_000.0}var ft:Double{returnself/3.28084}}
let oneInch =25.4.mm
println("One inch is \(oneInch) meters")// 打印输出:"One inch is 0.0254 meters"
let threeFeet =3.ft
println("Three feet is \(threeFeet) meters")// 打印输出:"Three feet is 0.914399970739201 meters"

这些计算属性表达的含义是把一个Double型的值看作是某单位下的长度值。即使它们被实现为计算型属性,但这些属性仍可以接一个带有dot语法的浮点型字面值,而这恰恰是使用这些浮点型字面量实现距离转换的方式。

在上述例子中,一个Double型的值1.0被用来表示“1米”。这就是为什么m计算型属性返回self——表达式1.m被认为是计算1.0Double值。

其它单位则需要一些转换来表示在米下测量的值。1千米等于1,000米,所以km计算型属性要把值乘以1_000.00来转化成单位米下的数值。类似地,1米有3.28024英尺,所以ft计算型属性要把对应的Double值除以3.28024来实现英尺到米的单位换算。

这些属性是只读的计算型属性,所有从简考虑它们不用get关键字表示。它们的返回值是Double型,而且可以用于所有接受Double的数学计算中:

let aMarathon =42.km+195.m
println("A marathon is \(aMarathon) meters long")// 打印输出:"A marathon is 42495.0 meters long"

注意:

扩展可以添加新的计算属性,但是不可以添加存储属性,也不可以向已有属性添加属性观测器(property observers)。

构造器(Initializers)

扩展可以向已有类型添加新的构造器。这可以让你扩展其它类型,将你自己的定制类型作为构造器参数,或者提供该类型的原始实现中没有包含的额外初始化选项。

注意:

如果你使用扩展向一个值类型添加一个构造器,该构造器向所有的存储属性提供默认值,而且没有定义任何定制构造器(custom initializers),那么对于来自你的扩展构造器中的值类型,你可以调用默认构造器(default initializers)和成员级构造器(memberwise initializers)。 正如在值类型的构造器授权中描述的,如果你已经把构造器写成值类型原始实现的一部分,上述规则不再适用。

下面的例子定义了一个用于描述几何矩形的定制结构体Rect。这个例子同时定义了两个辅助结构体SizePoint,它们都把0.0作为所有属性的默认值:

structSize{var width =0.0, height =0.0}structPoint{var x =0.0, y =0.0}structRect{var origin =Point()var size =Size()}

因为结构体Rect提供了其所有属性的默认值,所以正如默认构造器中描述的,它可以自动接受一个默认的构造器和一个成员级构造器。这些构造器可以用于构造新的Rect实例:

let defaultRect =Rect()
let memberwiseRect =Rect(origin:Point(x:2.0, y:2.0),
    size:Size(width:5.0, height:5.0))

你可以提供一个额外的使用特殊中心点和大小的构造器来扩展Rect结构体:

extension Rect{
    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)}}

这个新的构造器首先根据提供的centersize值计算一个合适的原点。然后调用该结构体自动的成员构造器init(origin:size:),该构造器将新的原点和大小存到了合适的属性中:

let centerRect =Rect(center:Point(x:4.0, y:4.0),
    size:Size(width:3.0, height:3.0))// centerRect的原点是 (2.5, 2.5),大小是 (3.0, 3.0)

注意:

如果你使用扩展提供了一个新的构造器,你依旧有责任保证构造过程能够让所有实例完全初始化。

方法(Methods)

扩展可以向已有类型添加新的实例方法和类型方法。下面的例子向Int类型添加一个名为repetitions的新实例方法:

extension Int{
    func repetitions(task:()->()){for i in0..self{
            task()}}}

这个repetitions方法使用了一个() -> ()类型的单参数(single argument),表明函数没有参数而且没有返回值。

定义该扩展之后,你就可以对任意整数调用repetitions方法,实现的功能则是多次执行某任务:

3.repetitions({
    println("Hello!")})// Hello!// Hello!// Hello!

可以使用 trailing 闭包使调用更加简洁:

3.repetitions{
    println("Goodbye!")}// Goodbye!// Goodbye!// Goodbye!

修改实例方法(Mutating Instance Methods)

通过扩展添加的实例方法也可以修改该实例本身。结构体和枚举类型中修改self或其属性的方法必须将该实例方法标注为mutating,正如来自原始实现的修改方法一样。

下面的例子向Swift的Int类型添加了一个新的名为square的修改方法,来实现一个原始值的平方计算:

extension Int{
    mutating func square(){self=self*self}}var someInt =3
someInt.square()// someInt 现在值是 9

下标(Subscripts)

扩展可以向一个已有类型添加新下标。这个例子向Swift内建类型Int添加了一个整型下标。该下标[n]返回十进制数字从右向左数的第n个数字

  • 123456789[0]返回9
  • 123456789[1]返回8

…等等

extension Int{
    subscript(digitIndex:Int)->Int{var decimalBase =1for _ in1...digitIndex {
                decimalBase *=10}return(self/ decimalBase)%10}}746381295[0]// returns 5746381295[1]// returns 9746381295[2]// returns 2746381295[8]// returns 7

如果该Int值没有足够的位数,即下标越界,那么上述实现的下标会返回0,因为它会在数字左边自动补0:

746381295[9]//returns 0, 即等同于:0746381295[9]

嵌套类型(Nested Types)

扩展可以向已有的类、结构体和枚举添加新的嵌套类型:

extension Character{enumKind{caseVowel,Consonant,Other}var kind:Kind{switchString(self).lowercaseString {case"a","e","i","o","u":return.Vowelcase"b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z":return.Consonantdefault:return.Other}}}

该例子向Character添加了新的嵌套枚举。这个名为Kind的枚举表示特定字符的类型。具体来说,就是表示一个标准的拉丁脚本中的字符是元音还是辅音(不考虑口语和地方变种),或者是其它类型。

这个类子还向Character添加了一个新的计算实例属性,即kind,用来返回合适的Kind枚举成员。

现在,这个嵌套枚举可以和一个Character值联合使用了:

func printLetterKinds(word:String){
    println("'\\(word)' is made up of the following kinds of letters:")for character in word {switch character.kind {case.Vowel:print("vowel ")case.Consonant:print("consonant ")case.Other:print("other ")}}print("\n")}
printLetterKinds("Hello")// 'Hello' is made up of the following kinds of letters:// consonant vowel consonant consonant vowel

函数printLetterKinds的输入是一个String值并对其字符进行迭代。在每次迭代过程中,考虑当前字符的kind计算属性,并打印出合适的类别描述。所以printLetterKinds就可以用来打印一个完整单词中所有字母的类型,正如上述单词"hello"所展示的。

注意:

由于已知character.kindCharacter.Kind型,所以Character.Kind中的所有成员值都可以使用switch语句里的形式简写,比如使用 .Vowel代替Character.Kind.Vowel

本文部分原文来自于http://www.swiftguide.cn/翻译小组的译文,共同校对中。

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/extensions/ ‎,商业转载请联系我们~ 感谢您对我们工作的支持~

Swift中文教程(19)类型嵌套

转载自letsswift.com

枚举类型常被用于实现特定类或结构体的功能。也能够在有多种变量类型的环境中,方便地定义通用类或结构体来使用,为了实现这种功能,Swift允许你定义类型嵌套,可以在枚举类型、类和结构体中定义支持嵌套的类型。

要在一个类型中嵌套另一个类型,将需要嵌套的类型的定义写在被嵌套类型的区域{}内,而且可以根据需要定义多级嵌套。

类型嵌套实例

下面这个例子定义了一个结构体BlackjackCard(二十一点),用来模拟BlackjackCard中的扑克牌点数。BlackjackCard结构体包含2个嵌套定义的枚举类型Suit 和 Rank

BlackjackCard规则中,Ace牌可以表示1或者11,Ace牌的这一特征用一个嵌套在枚举型Rank的结构体Values来表示。

structBlackjackCard{// 嵌套定义枚举型SuitenumSuit:Character{caseSpades="♠",Hearts="♡",Diamonds="♢",Clubs="♣"}// 嵌套定义枚举型RankenumRank:Int{caseTwo=2,Three,Four,Five,Six,Seven,Eight,Nine,TencaseJack,Queen,King,AcestructValues{
           let first:Int, second:Int?}var values:Values{switchself{case.Ace:returnValues(first:1, second:11)case.Jack,.Queen,.King:returnValues(first:10, second:nil)default:returnValues(first:self.toRaw(), second:nil)}}}// BlackjackCard 的属性和方法
    let rank:Rank, suit:Suitvar description:String{var output ="suit is \(suit.toRaw()),"
        output +=" value is \(rank.values.first)"if let second = rank.values.second {
            output +=" or \(second)"}return output
    }}

枚举型的Suit用来描述扑克牌的四种花色,并分别用一个Character类型的值代表花色符号。

枚举型的Rank用来描述扑克牌从Ace~10,J,Q,K,13张牌,并分别用一个Int类型的值表示牌的面值。(这个Int类型的值不适用于Ace,J,Q,K的牌)。

如上文所提到的,枚举型Rank在自己内部定义了一个嵌套结构体Values。这个结构体包含两个变量,只有Ace有两个数值,其余牌都只有一个数值。结构体Values中定义的两个属性:

first, 为Int second, 为 Int?, 或 “optional Int

Rank定义了一个计算属性values,这个计算属性会根据牌的面值,用适当的数值去初始化Values实例,并赋值给values。对于J,Q,K,Ace会使用特殊数值,对于数字面值的牌使用Int类型的值。

BlackjackCard结构体自身有两个属性—ranksuit,也同样定义了一个计算属性descriptiondescription属性用ranksuit的中内容来构建对这张扑克牌名字和数值的描述,并用可选类型second来检查是否存在第二个值,若存在,则在原有的描述中增加对第二数值的描述。

因为BlackjackCard是一个没有自定义构造函数的结构体,在Memberwise Initializers for Structure Types中知道结构体有默认的成员构造函数,所以你可以用默认的initializer去初始化新的常量theAceOfSpades:

let theAceOfSpades =BlackjackCard(rank:.Ace, suit:.Spades)
println("theAceOfSpades: \(theAceOfSpades.description)")// 打印出 "theAceOfSpades: suit is ♠, value is 1 or 11"

尽管RankSuit嵌套在BlackjackCard中,但仍可被引用,所以在初始化实例时能够通过枚举类型中的成员名称单独引用。在上面的例子中description属性能正确得输出对Ace牌有1和11两个值。

类型嵌套的引用

在外部对嵌套类型的引用,以被嵌套类型的名字为前缀,加上所要引用的属性名:

let heartsSymbol =BlackjackCard.Suit.Hearts.toRaw()// 红心的符号 为 "♡"

对于上面这个例子,这样可以使SuitRank, 和 Values的名字尽可能的短,因为它们的名字会自然的由被定义的上下文来限定。

本文部分原文来自于http://www.swiftguide.cn/翻译小组的译文,共同校对中。

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/nested-types/,商业转载请联系我们~ 感谢您对我们工作的支持~

Swift中文教程(18)类型检查

转载自letsswift.com

类型检查是一种检查类实例的方式,并且或者也是让实例作为它的父类或者子类的一种方式。

类型检查在 Swift 中使用is 和 as操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。

你也可以用来检查一个类是否实现了某个协议,就像在 Protocols Checking for Protocol Conformance部分讲述的一样。

定义一个类层次作为例子

你可以将它用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。这下面的三个代码段定义了一个类层次和一个包含了几个这些类实例的数组,作为类型检查的例子。

第一个代码片段定义了一个新的基础类MediaItem。这个类为任何出现在数字媒体库的媒体项提供基础功能。特别的,它声明了一个 String 类型的 name 属性,和一个init name初始化器。(它假定所有的媒体项都有个名称。)

classMediaItem{var name:String
    init(name:String){self.name = name
    }}

下一个代码段定义了 MediaItem 的两个子类。第一个子类Movie,在父类(或者说基类)的基础上增加了一个director(导演) 属性,和相应的初始化器。第二个类在父类的基础上增加了一个artist(艺术家) 属性,和相应的初始化器:

classSong:MediaItem{var artist:String
    init(name:String, artist:String){self.artist = artist
        super.init(name: name)}}

最后一个代码段创建了一个数组常量 library ,包含两个Movie实例和三个Song实例。library的类型是在它被初始化时根据它数组中所包含的内容推断来的。Swift 的类型检测器能够演绎出Movie和 Song 有共同的父类 MediaItem,所以它推断出 MediaItem[] 类作为 library 的类型。

let library =[Movie(name:"Casablanca", director:"Michael Curtiz"),Song(name:"Blue Suede Shoes", artist:"Elvis Presley"),Movie(name:"Citizen Kane", director:"Orson Welles"),Song(name:"The One And Only", artist:"Chesney Hawkes"),Song(name:"Never Gonna Give You Up", artist:"Rick Astley")]// the type of "library" is inferred to be MediaItem[]

在幕后library 里存储的媒体项依然是 Movie 和 Song 类型的,但是,若你迭代它,取出的实例会是 MediaItem 类型的,而不是 Movie 和 Song 类型的。为了让它们作为它们本来的类型工作,你需要检查它们的类型或者向下转换它们的类型到其它类型,就像下面描述的一样。

检查类型

用类型检查操作符(is)来检查一个实例是否属于特定子类型。类型检查操作符返回 true 若实例属于那个子类型,若不属于返回 false 。

下面的例子定义了两个变量,movieCount 和 songCount,用来计算数组library 中 Movie 和 Song 类型的实例数量。

var movieCount =0var songCount =0for item in library {if item isMovie{++movieCount
    }elseif item isSong{++songCount
    }}

println("Media library contains \(movieCount) movies and \(songCount) songs")// prints "Media library contains 2 movies and 3 songs"

示例迭代了数组 library 中的所有项。每一次, forin 循环设置 item 为数组中的下一个MediaItem

若当前 MediaItem 是一个 Movie 类型的实例, item is Movie 返回 true,相反返回 false。同样的,item is Song检查item是否为Song类型的实例。在循环结束后,movieCount 和 songCount的值就是被找到属于各自的类型的实例数量。

向下转型(Downcasting)

某类型的一个常量或变量可能在幕后实际上属于一个子类。你可以相信,上面就是这种情况。你可以尝试向下转到它的子类型,用类型检查操作符(as)

因为向下转型可能会失败,类型检查操作符带有两种不同形式。可选形式( optional form) as?返回一个你试图下转成的类型的可选值(optional value)。强制形式 as 把试图向下转型和强制解包(force-unwraps)结果作为一个混合动作。

当你不确定下转可以成功时,用类型检查的可选形式(as?)。可选形式的类型检查总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 nil 。这使你能够检查下转是否成功。

只有你可以确定下转一定会成功时,才使用强制形式。当你试图下转为一个不正确的类型时,强制形式的类型检查会触发一个运行时错误。

下面的例子,迭代了library里的每一个 MediaItem ,并打印出适当的描述。要这样做,item需要真正作为Movie或 Song的类型来使用。不仅仅是作为 MediaItem。为了能够使用Movie 或 Songdirector 或 artist属性,这是必要的。

在这个示例中,数组中的每一个item可能是 Movie 或 Song。 事前你不知道每个item的真实类型,所以这里使用可选形式的类型检查 (as?)去检查循环里的每次下转。

for item in library {if let movie = item as?Movie{
        println("Movie: '\(movie.name)', dir. \(movie.director)")}elseif let song = item as?Song{
        println("Song: '\(song.name)', by \(song.artist)")}}// Movie: 'Casablanca', dir. Michael Curtiz// Song: 'Blue Suede Shoes', by Elvis Presley// Movie: 'Citizen Kane', dir. Orson Welles// Song: 'The One And Only', by Chesney Hawkes// Song: 'Never Gonna Give You Up', by Rick Astley

示例首先试图将 item 下转为 Movie。因为 item 是一个 MediaItem 类型的实例,它可能是一个Movie;同样,它可能是一个 Song,或者仅仅是基类 MediaItem。因为不确定,as?形式在试图下转时将返还一个可选值。item as Movie 的返回值是Movie?类型或 “optional Movie”。

当下转为 Movie 应用在两个 Song 实例时将会失败。为了处理这种情况,上面的例子使用了可选绑定(optional binding)来检查可选 Movie真的包含一个值(这个是为了判断下转是否成功。)可选绑定是这样写的“if let movie = item as? Movie”,可以这样解读:

“尝试将 item 转为 Movie类型。若成功,设置一个新的临时常量 movie 来存储返回的可选Movie

若下转成功,然后movie的属性将用于打印一个Movie实例的描述,包括它的导演的名字director。当Song被找到时,一个相近的原理被用来检测 Song 实例和打印它的描述。

注意:

转换没有真的改变实例或它的值。潜在的根本的实例保持不变;只是简单地把它作为它被转换成的类来使用。

AnyAnyObject的类型检查

Swift为不确定类型提供了两种特殊类型别名:

  • AnyObject可以代表任何class类型的实例。
  • Any可以表示任何类型,除了方法类型(function types)。

注意:

只有当你明确的需要它的行为和功能时才使用AnyAnyObject。在你的代码里使用你期望的明确的类型总是更好的。

AnyObject类型

当需要在工作中使用 Cocoa APIs,它一般接收一个AnyObject[]类型的数组,或者说“一个任何对象类型的数组”。这是因为 Objective-C 没有明确的类型化数组。但是,你常常可以确定包含在仅从你知道的 API 信息提供的这样一个数组中的对象的类型。

在这些情况下,你可以使用强制形式的类型检查(as)来下转在数组中的每一项到比 AnyObject 更明确的类型,不需要可选解析(optional unwrapping)。

下面的示例定义了一个 AnyObject[] 类型的数组并填入三个Movie类型的实例:

let someObjects:AnyObject[]=[Movie(name:"2001: A Space Odyssey", director:"Stanley Kubrick"),Movie(name:"Moon", director:"Duncan Jones"),Movie(name:"Alien", director:"Ridley Scott")]

因为知道这个数组只包含 Movie 实例,你可以直接用(as)下转并解包到不可选的Movie类型(ps:其实就是我们常用的正常类型,这里是为了和可选类型相对比)。

forobjectin someObjects {
    let movie =objectasMovie
    println("Movie: '\(movie.name)', dir. \(movie.director)")}// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick// Movie: 'Moon', dir. Duncan Jones// Movie: 'Alien', dir. Ridley Scott

为了变为一个更短的形式,下转someObjects数组为Movie[]类型来代替下转每一项方式。

for movie in someObjects asMovie[]{
    println("Movie: '\(movie.name)', dir. \(movie.director)")}// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick// Movie: 'Moon', dir. Duncan Jones// Movie: 'Alien', dir. Ridley Scott
Any类型

这里有个示例,使用 Any 类型来和混合的不同类型一起工作,包括非class类型。它创建了一个可以存储Any类型的数组 things

var things =Any[]()

things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0,5.0))
things.append(Movie(name:"Ghostbusters", director:"Ivan Reitman"))

things 数组包含两个 Int 值,2个 Double 值,1个 String 值,一个元组 (Double, Double) ,Ivan Reitman 导演的电影“Ghostbusters”。

你可以在 switch cases里用is 和 as 操作符来发觉只知道是 Any 或 AnyObject的常量或变量的类型。 下面的示例迭代 things数组中的每一项的并用switch语句查找每一项的类型。这几种switch语句的情形绑定它们匹配的值到一个规定类型的常量,让它们可以打印它们的值:

for thing in things {switch thing {case0asInt:
        println("zero as an Int")case0asDouble:
        println("zero as a Double")case let someInt asInt:
        println("an integer value of \(someInt)")case let someDouble asDoublewhere someDouble >0:
        println("a positive double value of \(someDouble)")caseisDouble:
        println("some other double value that I don't want to print")case let someString asString:
        println("a string value of \"\(someString)\"")case let (x, y)as(Double,Double):
        println("an (x, y) point at \(x), \(y)")case let movie asMovie:
        println("a movie called '\(movie.name)', dir. \(movie.director)")default:
        println("something else")}}// zero as an Int// zero as a Double// an integer value of 42// a positive double value of 3.14159// a string value of "hello"// an (x, y) point at 3.0, 5.0// a movie called 'Ghostbusters', dir. Ivan Reitman

本文部分原文来自于http://www.swiftguide.cn/翻译小组的译文,共同校对中。

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/type-casting/ ,商业转载请联系我们~ 感谢您对我们工作的支持~

Swift中文教程(17)可选链

转载自letsswift.com

可选链(Optional Chaining)是一种可以请求和调用属性、方法及子脚本的过程,它的自判断性体现于请求或调用的目标当前可能为空(nil)。如果自判断的目标有值,那么调用就会成功;相反,如果选择的目标为空(nil),则这种调用将返回空(nil)。多次请求或调用可以被链接在一起形成一个链,如果任何一个节点为空(nil)将导致整个链失效。

注意: Swift 的自判断链和 Objective-C 中的消息为空有些相像,但是 Swift 可以使用在任意类型中,并且失败与否可以被检测到。

可选链可替代强制解析

通过在想调用的属性、方法、或子脚本的可选值(optional value)(非空)后面放一个问号,可以定义一个可选链。这一点很像在可选值后面放一个声明符号来强制拆得其封包内的值。他们的主要的区别在于当可选值为空时可选链即刻失败,然而一般的强制解析将会引发运行时错误。

为了反映可选链可以调用空(nil),不论你调用的属性、方法、子脚本等返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来检测你的可选链是否调用成功,有返回值即成功,返回nil则失败。

调用可选链的返回结果与原本的返回结果具有相同的类型,但是原本的返回结果被包装成了一个可选值,当可选链调用成功时,一个应该返回Int的属性将会返回Int?

下面几段代码将解释可选链和强制解析的不同。

首先定义两个类PersonResidence

classPerson{var residence:Residence?}classResidence{var numberOfRooms =1}

Residence具有一个Int类型的numberOfRooms,其值为 1。Person具有一个自判断residence属性,它的类型是Residence?

如果你创建一个新的Person实例,它的residence属性由于是被定义为自判断型的,此属性将默认初始化为空:

let john =Person()

如果你想使用感叹号(!)强制解析获得这个人residence属性numberOfRooms属性值,将会引发运行时错误,因为这时没有可以供解析的residence值。

let roomCount = john.residence!.numberOfRooms
//将导致运行时错误

john.residence不是nil时,会运行通过,且会将roomCount 设置为一个int类型的合理值。然而,如上所述,当residence为空时,这个代码将会导致运行时错误。

可选链提供了一种另一种获得numberOfRooms的方法。利用可选链,使用问号来代替原来!的位置:

if let roomCount = john.residence?.numberOfRooms {
    println("John's residence has \(roomCount) room(s).")}else{
    println("Unable to retrieve the number of rooms.")}// 打印 "Unable to retrieve the number of rooms.

这告诉 Swift 来链接自判断residence?属性,如果residence存在则取回numberOfRooms的值。

因为这种尝试获得numberOfRooms的操作有可能失败,可选链会返回Int?类型值,或者称作“自判断Int”。当residence是空的时候(上例),选择Int将会为空,因此会出先无法访问numberOfRooms的情况。

要注意的是,即使numberOfRooms是非自判断IntInt?)时这一点也成立。只要是通过可选链的请求就意味着最后numberOfRooms总是返回一个Int?而不是Int

你可以自己定义一个Residence实例给john.residence,这样它就不再为空了:

john.residence =Residence()

john.residence 现在有了实际存在的实例而不是nil了。如果你想使用和前面一样的可选链来获得numberOfRoooms,它将返回一个包含默认值 1 的Int?

if let roomCount = john.residence?.numberOfRooms {
    println("John's residence has \(roomCount) room(s).")}else{
    println("Unable to retrieve the number of rooms.")}// 打印 "John's residence has 1 room(s)"。

为可选链定义模型类

你可以使用可选链来多层调用属性,方法,和子脚本。这让你可以利用它们之间的复杂模型来获取更底层的属性,并检查是否可以成功获取此类底层属性。

后面的代码定义了四个将在后面使用的模型类,其中包括多层可选链。这些类是由上面的PersonResidence模型通过添加一个Room和一个Address类拓展来。

Person类定义与之前相同。

classPerson{var residence:Residence?}

Residence类比之前复杂些。这次,它定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组:

classResidence{var rooms =Room[]()var numberOfRooms:Int{return rooms.count
    }
    subscript(i:Int)->Room{return rooms[i]}
    func printNumberOfRooms(){
        println("The number of rooms is \(numberOfRooms)")}var address:Address?}

因为Residence存储了一个Room实例的数组,它的numberOfRooms属性值不是一个固定的存储值,而是通过计算而来的。numberOfRooms属性值是由返回rooms数组的count属性值得到的。

为了能快速访问rooms数组,Residence定义了一个只读的子脚本,通过插入数组的元素角标就可以成功调用。如果该角标存在,子脚本则将该元素返回。

Residence中也提供了一个printNumberOfRooms的方法,即简单的打印房间个数。

最后,Residence定义了一个自判断属性叫addressaddress?)。Address类的属性将在后面定义。 用于rooms数组的Room类是一个很简单的类,它只有一个name属性和一个设定room名的初始化器。

classRoom{
    let name:String
    init(name:String){self.name = name }}

这个模型中的最终类叫做Address。它有三个自判断属性他们额类型是String?。前面两个自判断属性buildingNamebuildingNumber作为地址的一部分,是定义某个建筑物的两种方式。第三个属性street,用于命名地址的街道名:

classAddress{var buildingName:String?var buildingNumber:String?var street:String?
    func buildingIdentifier()->String?{if buildingName {return buildingName
        }elseif buildingNumber {return buildingNumber
        }else{returnnil}}}

Address类还提供了一个buildingIdentifier的方法,它的返回值类型为String?。这个方法检查buildingNamebuildingNumber的属性,如果buildingName有值则将其返回,或者如果buildingNumber有值则将其返回,再或如果没有一个属性有值,返回空。

通过可选链调用属性

正如上面“ 可选链可替代强制解析”中所述,你可以利用可选链的可选值获取属性,并且检查属性是否获取成功。然而,你不能使用可选链为属性赋值。

使用上述定义的类来创建一个人实例,并再次尝试后去它的numberOfRooms属性:

let john =Person()if let roomCount = john.residence?.numberOfRooms {
    println("John's residence has \(roomCount) room(s).")}else{
    println("Unable to retrieve the number of rooms.")}// 打印 "Unable to retrieve the number of rooms。

由于john.residence是空,所以这个可选链和之前一样失败了,但是没有运行时错误。

通过可选链调用方法

你可以使用可选链的来调用可选值的方法并检查方法调用是否成功。即使这个方法没有返回值,你依然可以使用可选链来达成这一目的。

ResidenceprintNumberOfRooms方法会打印numberOfRooms的当前值。方法如下:

func printNumberOfRooms(){
    println(“The number of rooms is \(numberOfRooms)”)}

这个方法没有返回值。但是,没有返回值类型的函数和方法有一个隐式的返回值类型Void(参见Function Without Return Values)。

如果你利用可选链调用此方法,这个方法的返回值类型将是Void?,而不是Void,因为当通过可选链调用方法时返回值总是可选类型(optional type)。,即使是这个方法本是没有定义返回值,你也可以使用if语句来检查是否能成功调用printNumberOfRooms方法:如果方法通过可选链调用成功,printNumberOfRooms的隐式返回值将会是Void,如果没有成功,将返回nil

if john.residence?.printNumberOfRooms(){
    println("It was possible to print the number of rooms.")}else{
    println("It was not possible to print the number of rooms.")}// 打印 "It was not possible to print the number of rooms."。

使用可选链调用子脚本

你可以使用可选链来尝试从子脚本获取值并检查子脚本的调用是否成功,然而,你不能通过可选链来设置子代码。

注意: 当你使用可选链来获取子脚本的时候,你应该将问号放在子脚本括号的前面而不是后面。可选链的问号一般直接跟在自判断表达语句的后面。

下面这个例子用在Residence类中定义的子脚本来获取john.residence数组中第一个房间的名字。因为john.residence现在是nil,子脚本的调用失败了。

if let firstRoomName = john.residence?[0].name {
    println("The first room name is \(firstRoomName).")}else{
    println("Unable to retrieve the first room name.")}// 打印 "Unable to retrieve the first room name."。

在子代码调用中可选链的问号直接跟在john.residence的后面,在子脚本括号的前面,因为john.residence是可选链试图获得的可选值。

如果你创建一个Residence实例给john.residence,且在他的rooms数组中有一个或多个Room实例,那么你可以使用可选链通过Residence子脚本来获取在rooms数组中的实例了:

let johnsHouse =Residence()
johnsHouse.rooms +=Room(name:"Living Room")
johnsHouse.rooms +=Room(name:"Kitchen")
john.residence = johnsHouse

if let firstRoomName = john.residence?[0].name {
    println("The first room name is \(firstRoomName).")}else{
    println("Unable to retrieve the first room name.")}// 打印 "The first room name is Living Room."。

连接多层链接

你可以将多层可选链连接在一起,可以掘取模型内更下层的属性方法和子脚本。然而多层可选链不能再添加比已经返回的可选值更多的层。 也就是说:

如果你试图获得的类型不是可选类型,由于使用了可选链它将变成可选类型。 如果你试图获得的类型已经是可选类型,由于可选链它也不会提高自判断性。

因此:

如果你试图通过可选链获得Int值,不论使用了多少层链接返回的总是Int?。 相似的,如果你试图通过可选链获得Int?值,不论使用了多少层链接返回的总是Int?

下面的例子试图获取johnresidence属性里的addressstreet属性。这里使用了两层可选链来联系residenceaddress属性,他们两者都是可选类型:

if let johnsStreet = john.residence?.address?.street {
    println("John's street name is \(johnsStreet).")}else{
    println("Unable to retrieve the address.")}// 打印 "Unable to retrieve the address.”。

john.residence的值现在包含一个Residence实例,然而john.residence.address现在是nil,因此john.residence?.address?.street调用失败。

从上面的例子发现,你试图获得street属性值。这个属性的类型是String?。因此尽管在可选类型属性前使用了两层可选链,john.residence?.address?.street的返回值类型也是String?

如果你为Address设定一个实例来作为john.residence.address的值,并为addressstreet属性设定一个实际值,你可以通过多层可选链来得到这个属性值。

let johnsAddress =Address()
johnsAddress.buildingName ="The Larches"
johnsAddress.street ="Laurel Street"
john.residence!.address = johnsAddress

if let johnsStreet = john.residence?.address?.street {
    println("John's street name is \(johnsStreet).")}else{
    println("Unable to retrieve the address.")}// 打印 "John's street name is Laurel Street."。

值得注意的是,“!”符的在定义address实例时的使用(john.residence.address)。john.residence属性是一个可选类型,因此你需要在它获取address属性之前使用!解析以获得它的实际值。

链接自判断返回值的方法

前面的例子解释了如何通过可选链来获得可选类型属性值。你也可以通过调用返回可选类型值的方法并按需链接方法的返回值。

下面的例子通过可选链调用了Address类中的buildingIdentifier 方法。这个方法的返回值类型是String?。如上所述,这个方法在可选链调用后最终的返回值类型依然是String?

if let buildingIdentifier = john.residence?.address?.buildingIdentifier(){
    println("John's building identifier is \(buildingIdentifier).")}// 打印 "John's building identifier is The Larches."。

如果你还想进一步对方法返回值执行可选链,将可选链问号符放在方法括号的后面:

if let upper = john.residence?.address?.buildingIdentifier()?.uppercaseString {
    println("John's uppercase building identifier is \(upper).")}// 打印 "John's uppercase building identifier is THE LARCHES."。

注意: 在上面的例子中,你将可选链问号符放在括号后面是因为你想要链接的可选值是buildingIdentifier方法的返回值,不是buildingIdentifier方法本身。

本文部分原文来自于http://www.swiftguide.cn/翻译小组的译文,共同校对中。

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/optional-chaining/ ,商业转载请联系我们~ 感谢您对我们工作的支持~

Swift中文教程(16)自动引用计数

转载自letsswift.com

Swift使用自动引用计数(ARC)来管理应用程序的内存使用。这表示内存管理已经是Swift的一部分,在大多数情况下,你并不需要考虑内存的管理。当实例并不再被需要时,ARC会自动释放这些实例所使用的内存。

但是,少数情况下,你必须提供部分代码的额外信息给ARC,这样它才能够帮你管理这部分内存。本章阐述了这些情况并且展示如何使用ARC来管理应用程序的内存。

注意
引用计数仅仅作用于类实例上。结构和枚举是值类型,而非引用类型,所以不能被引用存储和传递。

1、ARC怎样工作
每当你创建一个类的实例,ARC分配一个内存块来存储这个实例的信息,包含了类型信息和实例的属性值信息。

另外当实例不再被使用时,ARC会释放实例所占用的内存,这些内存可以再次被使用。

但是,如果ARC释放了正在被使用的实例,就不能再访问实例属性,或者调用实例的方法了。直接访问这个实例可能造成应用程序的崩溃。

为了保证需要实例时实例是存在的,ARC对每个类实例,都追踪有多少属性、常量、变量指向这些实例。当有活动引用指向它时,ARC是不会释放这个实例的。

为实现这点,当你将类实例赋值给属性、常量或变量时,指向实例的一个强引用(strong reference)将会被构造出来。被称为强引用是因为它稳定地持有这个实例,当这个强引用存在是,实例就不能够被释放。

2、ARC实例
下面的例子展示了ARC是怎样工作的。定义一个简单的类Person,包含一个存储常量属性name:

12345678910
classPerson{
    let name:String
    init(name:String){
        self.name = name
        println("\(name) is being initialized")
    }
    deinit {
        println("\(name) is being deinitialized")
    }}

Person类有一个初始化方法来设置属性name并打印一条信息表明这个初始化过程。还有一个析构方法打印实例被释放的信息。

下面的代码定义了是三个Person?类型的变量,随后的代码中,这些变量用来设置一个Person实例的多重引用。因为这些变量是可选类型(Person?),它们自动被初始化为nil,并且不应用任何Person实例。

123
var reference1:Person?var reference2:Person?var reference3:Person?

现在你可以创建一个Person实例并赋值给其中一个变量:

12
reference1 =Person(name:"John Appleseed")// prints "John Appleseed is being initialized"

注意这条信息:““John Appleseed is being initialized”,指出类Person的构造器已经被调用。

因为新的Person实例被赋值给变量reference1,因此这是一个强引用。由于有一个强引用的存在,ARC保证了Person实例在内存中不被释放掉。

如果你将这个Person实例赋值给更多的变量,就建立了相应数量的强引用:

12
reference2 = reference1
reference3 = reference1

现在有三个强引用指向这个Person实例了。

如果你将nil赋值给其中两个变量从而切断了这两个强引用(包含原始引用),还有一个强引用是存在的,因此Person实例不被释放。

12
reference1 =nil
reference2 =nil

直到第三个强引用被破坏之后,ARC才释放这个Person实例,因此之后你就不能在使用这个实例了:

1
reference3 nil

3、类实例间的强引用循环

在上面的例子中,ARC跟踪指向Person实例的引用并保证只在Person实例不再被使用后才释放。

但是,写出一个类的实例没有强引用指向它这样的代码是可能的。试想,如果两个类实例都有一个强引用指向对方,这样的情况就是强引用循环。

通过在类之间定义弱的(weak)或无主的(unowned)引用可以解决强引用循环这个问题。这些方法在“解决类实例间的强引用循环“(“Resolving Strong Reference Cycles Between Class Instances”)描述。但是,在学习怎样解决这个问题前,先来理解这样的循环是怎样造成的。

下面的例子描述了强引用循环是怎样无意中被造成的。定义了两个类Person和Apartment,建立了公寓和它的住户间的关系

123456789101112
classPerson{
    let name:String
    init(name:String){self.name = name }
    var apartment:Apartment?
    deinit { println("\(name) is being deinitialized")}}classApartment{
    let number:Int
    init(number:Int){self.number = number }
    var tenant:Person?
    deinit { println("Apartment #\(number) is being deinitialized")}}

每个Person实例有一个String类型的属性name和一个初值为nil的可选属性apartment。之所以apartment是可选属性,因为一个住户可能并没有一个公寓。

同样,每个Apartment实例有一个Int类型的属性number和一个初值为nil的可选属性tenant,一个公寓(apartment)并不总是有人居住,所以tenant是可选属性。

两个类都定义了析构方法,打印表明类实例被析构的语句,这告诉你Person和Apartment实例是否如愿的被释放掉了。

下面的代码定义了两个可选类型变量john和number73,将用来设置之后的Apartment和Person实例。两个变量都被初始化为nil:

12
var john:Person?var number73:Apartment?

下面创建两个Person实例和Apartment实例赋值给上面的变量:

12
john =Person(name:"John Appleseed")
number73 =Apartment(number:73)

下面的图表明在创建这两个实例并赋值后的样子,变量john有一个指向Person实例的强引用,变量number73有一个指向Apartment实例的强引用:
image: ../Art/referenceCycle01_2x.png

现在你可以将这两个实例连接起来,使得一个住户与一个公寓一一对应起来,注意感叹号用来解开(?unwrap)并访问john和number73中的可选变量,因此属性可以被设置:

12
john!.apartment = number73
number73!.tenant = john

下图是连接后的强引用管理图示:
image: ../Art/referenceCycle02_2x.png

不幸的是,这样做造成了两个实例间的强引用循环。因此,当你破坏john和number73变量间的强引用、时,引用计数并没有减少到0,ARC也不会释放实例:

12
john =nil
number73 =nil

当你将两个变量设置为nil时,各自的析构方法都不会被调用到。强引用循环防止了Person和Apartment实例被释放造成的内存泄漏。

下图是设置john和number73为nil后的情况:
image: ../Art/referenceCycle03_2x.png

Person实例和Apartment之间的强引用并没有被破坏掉。

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

Swift提供了两种方法解决类实例属性间的强引用循环:弱引用和无主(unowned)引用。

弱引用和无主引用使得一个引用循环中实例并不需要强引用就可以指向循环中的其他实例。互相引用的实例就不用形成一个强引用循环。

当在生命周期的某些时刻引用可能变为nil时使用弱引用。相反,当引用在初始化期间被设置后不再为nil时使用无主引用。

弱引用

弱引用并不保持对所指对象的强烈持有,因此并不阻止ARC对引用实例的回收。这个特性保证了引用不成为强引用循环的一部分。指明引用为弱引用是在生命属性或变量时在其前面加上关键字weak。

使用弱引用不管在生命周期的某时刻是否有值。如果引用一直有值,使用无主引用(见无主引用节)。在上例中,公寓不可能一直都有住户,所以应该使用弱引用,来打破强引用循环。

注意
弱引用必须声明为变量,指明它们的值在运行期可以改变。弱引用不能被声明为常量。

因为弱引用可以不含有值,所以必须声明弱引用为可选类型。因为可选类型使得Swift中的不含有值成为可能。

因为弱引用的这个特性,所以当弱引用指向实例时实例仍然可以被释放。实例释放后,ARC将弱引用的值设置为nil。你可以想其他可选类型一样检查弱引用的值,你也不会使用所指向实例不存在的的引用。

与上面的例子不同,下例声明了Apartment的属性tenant为弱引用:

123456789101112
classPerson{
    let name:String
    init(name:String){self.name = name }
    var apartment:Apartment?
    deinit { println("\(name) is being deinitialized")}}classApartment{
    let number:Int
    init(number:Int){self.number = number }
    weak var tenant:Person?
    deinit { println("Apartment #\(number) is being deinitialized")}}

依然像前例一样创建两个变量(john和number73)和它们之间的强引用:

123456
var john:Person?var number73:Apartment?
john =Person(name:"John Appleseed")
number73 =Apartment(number:73)
john!.apartment = number73
number73!.tenant = john

下图是两个实例之间是怎样引用的:
image: ../Art/weakReference01_2x.png

Person实例仍然有一个到实例Apartment的强引用,相反实例Apartment实例只有一个到Person实例弱引用。意味着当你破坏john变量持有的强引用时,到Person实例的强引用就不存在了。
image: ../Art/weakReference02_2x.png

因为没有到Person实例的强引用,实例可以释放:

1
john nil

仅存的强引用是从变量number73到Apartment实例的强引用,当你破坏它时,这个强引用就不存在了:
image: ../Art/weakReference03_2x.png

因此可以释放Person:

1
number73 nil

上面两段代码展示了Person实例和Aparment实例的析构,当两个变量分别被设置为nil时,就打印各自的析构提示信息,展示了引用循环被打破了。

无主引用

和弱引用一样,无主引用也并不持有实例的强引用。但和弱引用不同的是,无主引用通常都有一个值。因此,无主引用并不定义成可选类型。指明为无主引用是在属性或变量声明的时候在之前加上关键字unowned。

因为无主引用非可选类型,所以每当使用无主引用时不必解开(unwrap?)它。无主引用通常可以直接访问。但是当无主引用所指实例被释放时,ARC并不能将引用值设置为nil,因为非可选类型不能设置为nil。

注意
在无主引用指向实例被释放后,如果你像访问这个无主引用,将会触发一个运行期错误(仅当能够确认一个引用一直指向一个实例时才使用无主引用)。在Swift中这种情况也会造成应用程序的崩溃,会有一些不可预知的行为发生,尽管你可能已经采取了一些预防措施

接下来的例子定义了两个类,Customer和CreditCard,表示一个银行客户和信用卡。这两个类的属性各自互相存储对方类实例。这种关系存在着潜在的强引用循环。

这两个类之间的关系稍微和前面的Person和Apartment有些不同。在此例中,一个客户可能有也可能没有一个信用卡,但是一个信用卡必须由一个客户持有。因此,类Customer有一个可选的card熟悉,而类CreditCard有一个非可选customer属性。

另外,创建CreditCard实例时必须必须向其构造器传递一个值number和一个customer实例。这保证了信用卡实例总有一个客户与之联系在一起。

因为信用卡总由一个用户持有,所以定义customer属性为无主引用,来防止强引用循环。

1234567891011121314151617
classCustomer{
    let name:String
    var card:CreditCard?
    init(name:String){
        self.name = name
    }
    deinit { println("\(name) is being deinitialized")}}classCreditCard{
    let number:Int
    unowned let customer:Customer
    init(number:Int, customer:Customer){
        self.number = number
        self.customer = customer
    }
    deinit { println("Card #\(number) is being deinitialized")}}

下面的代码段定义了Customer类型可选变量john,用来存储一个特定用户的引用,这个变量初值为nil:

1
var john:Customer?

现在可以创建一个Customer实例,并初始化一个新的CreditCard实例来设置customer实例的card属性:

12
john =Customer(name:"John Appleseed")
john!.card =CreditCard(number:1234_5678_9012_3456, customer: john!)

下图是引用间的连接管理:
image: ../Art/unownedReference01_2x.png

Customer实例有一个到CreditCard实例的强引用,CreaditCard实例有一个到Customer实例无主引用。

因为无主引用的存在,当你破坏变量john持有的强引用时,就再也没有到Customer实例的强引用了。

image: ../Art/unownedReference02_2x.png

因为没有到Customer实例的强引用,实例被释放了。之后,到CreditCard实例的强引用也不存在了,因此这个实例也被释放了:

123
john =nil// prints "John Appleseed is being deinitialized"// prints "Card #1234567890123456 is being deinitialized"

上面的代码段显示了变量john设置为nil后Customer实例和CreditCard实例被析构的信息。

无主引用和隐式拆箱可选属性
上面的弱引用和无主引用例子是更多常见场景中的两个,表面打破强引用循环是必要的。

例子Person和Apartment显示了当互相引用的两个属性被设置为nil时可能造成强引用循环。这种情况可以使用弱引用来解决。

例子Customer和CreditCard显示了一个属性可以设置为nil,而另一个不可以为nil时可能造成的强引用循环。这种情况可以使用无主引用解决。

但是,有第三种情况,两个属性都一直有值,并且都不可以被设置为nil。这种情况下,通常联合一个类种的无主属性和一个类种的隐式装箱可选属性(implicitly unwrapped optional property)。

这保证了两个属性都可以被直接访问,并且防止了引用循环。这一节展示了如何进行这样的设置。

下面的例子定义了两个类,Coutry和City,两者的属性都存放另外一个类的实例。在数据模型中,每个国家都有一个首都,而每个城市都属于一个国家。代码如下:

12345678910111213141516
classCountry{
    let name:String
    let capitalCity:City!
    init(name:String, capitalName:String){
        self.name = name
        self.capitalCity =City(name: capitalName, country:self)
    }}classCity{
    let name:String
    unowned let country:Country
    init(name:String, country:Country){
        self.name = name
        self.country = country
    }}

为表达这样的关系,City的构造器有一个参数为Country实例,并将他存为country属性。

City的构造器也在Country的构造器中被调用。但是直到一个新的Country实例被完整地初始化,Country构造器不能传递self给City的构造器(在两阶段初始化“Two-Phase Initialization”节描述)

为处理这样的情况,声明Country的属性capitalCity属性为隐式拆箱可选属性,通过在类型注解后加检测符号实现(City!)。这表明capitalCity属性像其他可选属性一样有一个默认值nil,但是可以不需要对值拆箱就可以访问(隐式拆箱选项(implicitly unwrapped optionals)描述)

因为capitalCity有一个默认值nil,所以当在Country的构造器中设置了name属性的值后,一个新的Country实例就完全地被初始化了,这表明Country构造器已经可以传递隐式self属性,从而设置capitalCity属性值。

这表明你可以在一个单独的语句中创建Country和CIty实例,不会造成强引用循环,并且可以不用使用检测符号(!)解包可选值来直接访问capitalCity属性:

123
var country =Country(name:"Canada", capitalName:"Ottawa")
println("\(country.name)'s capital city is called \(country.capitalCity.name)")// prints "Canada's capital city is called Ottawa"

上面的例子中,隐式解包选项的使用表示分阶段初始化是可行的。当初始化完成时,capitalCity属性可以像一个非可选值那样访问,并且不会造成强引用循环。

5、闭包的强引用循环

前面你知道了当两个类实例持有对方的强引用时强引用循环是怎样被创建的。你也知道了怎样使用弱引用和无主引用来破坏强引用循环。

当将一个闭包赋值给一个类实例的属性,并且闭包体捕获这个实例时,也可能存在一个强引用循环。捕获实例是因为闭包体访问了实例的属性,就像self.someProperty,或者调用了实例的方法,就像self.someMethod()。不管哪种情况,这都造成闭包捕获self,造成强引用循环。

这个强引用循环的存在是因为闭包和类一样都是引用类型。当你将闭包赋值给属性时,就给这个闭包赋值了一个引用。本质上和前面的问题相同-两个强引用都互相地指向对方。但是,与两个类实例不同,这里是一个类与一个闭包。

Swift为这个问题提供了一个优美的解决方法,就是闭包捕获列表。但是,在学习怎样通过闭包捕获列表破坏强引用循环以前,有必要了解这样的循环是怎样造成的。

下面的例子展示了当使用闭包引用self时强引用循环是怎样造成当。定义了一个名为HTMLElement的类,建模了HTML文档中的一个单独的元素:

123456789101112131415161718
classHTMLElement{
    let name:String
    let text:String?
    @lazyvar asHTML:()->String={
        if let text =self.text {
            return"<\(self.name)>\(text)"
        }else{
            return"<\(self.name) />"
        }
    }
    init(name:String, text:String?=nil){
        self.name = name
        self.text = text
    }
    deinit {
        println("\(name) is being deinitialized")
    }}

这个HTMLElement类定义了一个表示元素(例如“p“,”br“)名称的属性name,和一个可选属性text,表示要在页面上渲染的html元素的字符串的值

另外,还定义了一个懒惰属性asHTML。这个属性引用一个闭包,这个闭包结合name与text形成一个html代码字符串。这个属性类型是()-> String,表示一个函数不需要任何参数,返回一个字符串值。

默认地,asHTML属性赋值为返回HTML标签字符串的闭包。这个标签包含了可选的text值。对一个段落而言,闭包返回”<p>some text</p>”或者”<p />”,取决其中的text属性为“some text”还是nil。

asHTML属性的命名和使用都和实例方法类似,但是,因为它是一个闭包属性,如果想渲染特定的html元素,你可以使用另外一个闭包来代替asHTML属性的默认值。

这个HTMLElement类提供单一的构造器,传递一个name和一个text参数。定义了一个析构器,打印HTMLElement实例的析构信息。

下面是如何使用HTMLElement类来创建和打印一个新的实例:

123
var paragraph:HTMLElement?=HTMLElement(name:"p", text:"hello, world")
println(paragraph!.asHTML())// prints "hello, world"

不幸的是,上面所写的HTMLElemnt类的实现会在HTMLElement实例和闭包所使用的默认asHTML值之间造成强引用循环,下面是其图示:
image: ../Art/closureReferenceCycle01_2x.png

实例的asHTML属性持有其闭包的一个强引用,但是因为闭包在其类内引用self(self.name和self.text方式),闭包捕获类本身,意味着它也持有到HTMLElement实例的引用。强引用循环就这样建立了。(关于闭包捕获值的更多信息,参见CapturingValues)

如果设置paragraph变量值为nil,破坏了到HTMLElement实例的强引用,实例和其闭包都不会被析构,因为强引用循环:

1
paragraph nil

注意HTMLElement析构器中的提示信息不会被打印,表示HTMLElement实例并没有被析构。

6、解决闭包的强引用循环
通过定义捕获列表为闭包的一部分可以解决闭包和类实例之间的强引用循环。捕获列表定义了在闭包体内何时捕获一个或多个引用类型的规则。像解决两个类实例之间的强引用循环一样,你声明每个捕获引用为弱引用或者无主引用。究竟选择哪种定义取决于代码中其他部分间的关系

定义捕获列表
捕获列表中的每个元素由一对weak/unowned关键字和类实例(self或someInstance)的引用所组成。这些对由方括号括起来并由都好分隔。

将捕获列表放在闭包参数列表和返回类型(如果提供)的前面:

1234
@lazyvar someClosure:(Int,String)->String={
    [unowned self](index:Int, stringToProcess:String)->Stringin
    // closure body goes here}

如果闭包没有包含参数列表和返回值,它们可以从上下文中推断出来的话,将捕获列表放在闭包的前面,后面跟着关键字in:

1234
@lazyvar someClosure:()->String={
    [unowned self]in
    // closure body goes here}

弱引用和无主引用
当闭包和实例之间总是引用对方并且同时释放时,定义闭包捕获列表为无主引用。
当捕获引用可能为nil,定义捕获列表为弱引用。弱引用通常是可选类型,并且在实例释放后被设置为nil。这使得你可以在闭包体内检查实例是否存在。

在例子HTMLElement中,可以使用无主引用来解决强引用循环问题,下面是其代码:

12345678910111213141516171819
classHTMLElement{
    let name:String
    let text:String?
    @lazyvar asHTML:()->String={
        [unowned self]in
        if let text =self.text {
            return"<\(self.name)>\(text)"
        }else{
            return"<\(self.name) />"
        }
    }
    init(name:String, text:String?=nil){
        self.name = name
        self.text = text
    }
    deinit {
        println("\(name) is being deinitialized")
    }}

这个HTMLELement实现在之前的基础上在asHTML闭包中加上了捕获列表。这里,捕获列表是[unowned self],表示作为无主引用来捕获自己而不是强引用。

你可以像之间一样创建和打印HTMLElement实例:

123
var paragraph:HTMLElement?=HTMLElement(name:"p", text:"hello, world")
println(paragraph!.asHTML())// prints "hello, world"

下图是使用捕获列表后的引用图示:
image: ../Art/closureReferenceCycle02_2x.png

此时,闭包捕获自身是一个无主引用,并不持有捕获HTMLelement实例的强引用。如果你设置paragraph的强引用为nil,HTMLElement实例就被释放了,可以从析构信息中看出来:

12
paragraph =nil// prints "p is being deinitialized"

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、约书亚、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/automatic-reference-counting,商业转载请联系我们~ 感谢您对我们工作的支持~

Swift中文教程(15)析构

转载自letsswift.com

在一个类的实例被释放之前,析构函数会被调用。用关键字deinit来定义析构函数,类似于初始化函数用init来定义。析构函数只适用于class类型。

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

在类的定义中,每个类最多只能有一个析构函数。析构函数不带任何参数,在写法上不带括号:

deinit {
// 执行析构过程
}
析构函数是在实例释放发生前一步被自动调用。不允许主动调用自己的析构函数。子类继承了父类的析构函数,并且在子类析构函数实现的最后,父类的析构函数被自动调用。即使子类没有提供自己的析构函数,父类的析构函数也总是被调用。

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

2、析构器操作
这里是一个析构函数操作的例子。这个例子是一个简单的游戏,定义了两种新类型,Bank和Player。Bank结构体管理一个虚拟货币的流通,在这个流通中Bank永远不可能拥有超过 10,000 的硬币。在这个游戏中有且只能有一个Bank存在,因此Bank由带有静态属性和静态方法的结构体实现,从而存储和管理其当前的状态。

struct Bank {
static var coinsInBank = 10_000
static func vendCoins(var numberOfCoinsToVend: Int) -> Int {
numberOfCoinsToVend = min(numberOfCoinsToVend, coinsInBank)
coinsInBank -= numberOfCoinsToVend
return numberOfCoinsToVend
}
static func receiveCoins(coins: Int) {
coinsInBank += coins
}
}
Bank根据它的coinsInBank属性来跟踪当前它拥有的硬币数量。银行还提供两个方法——vendCoins和receiveCoins——用来处理硬币的分发和收集。

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

receiveCoins方法只是将 bank 的硬币存储和接收到的硬币数目相加,再保存回 bank。

Player类描述了游戏中的一个玩家。每一个 player 在任何时刻都有一定数量的硬币存储在他们的钱包中。这通过 player 的coinsInPurse属性来体现:

class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = Bank.vendCoins(coins)
}
func winCoins(coins: Int) {
coinsInPurse += Bank.vendCoins(coins)
}
deinit {
Bank.receiveCoins(coinsInPurse)
}
}
每个Player实例都由一个指定数目硬币组成的启动额度初始化,这些硬币在 bank 初始化的过程中得到。如果没有足够的硬币可用,Player实例可能收到比指定数目少的硬币。

Player类定义了一个winCoins方法,该方法从银行获取一定数量的硬币,并把它们添加到玩家的钱包。Player类还实现了一个析构函数,这个析构函数在Player实例释放前一步被调用。这里析构函数只是将玩家的所有硬币都返回给银行:

var playerOne: Player? = Player(coins: 100)
println(“A new player has joined the game with (playerOne!.coinsInPurse) coins”)
// 输出 “A new player has joined the game with 100     coins”
println(“There are now (Bank.coinsInBank) coins left     in the bank”)
// 输出 “There are now 9900 coins left in the bank”
一个新的Player实例随着一个 100 个硬币(如果有)的请求而被创建。这个Player实例存储在一个名为playerOne的可选Player变量中。这里使用一个可选变量,是因为玩家可以随时离开游戏。设置为可选使得你可以跟踪当前是否有玩家在游戏中。

因为playerOne是可选的,所以由一个感叹号(!)来修饰,每当其winCoins方法被调用时,coinsInPurse属性被访问并打印出它的默认硬币数目。

playerOne!.winCoins(2_000)
println(“PlayerOne won 2000 coins & now has \    (playerOne!.coinsInPurse) coins”)
// 输出 “PlayerOne won 2000 coins & now has 2100 coins”
println(“The bank now only has (Bank.coinsInBank) coins left”)
// 输出 “The bank now only has 7900 coins left”
这里,player 已经赢得了 2,000 硬币。player 的钱包现在有 2,100 硬币,bank 只剩余 7,900 硬币。

playerOne = nil
println(“PlayerOne has left the game”)
// 输出 “PlayerOne has left the game”
println(“The bank now has (Bank.coinsInBank) coins”)
// 输出 “The bank now has 10000 coins”
玩家现在已经离开了游戏。这表明是要将可选的playerOne变量设置为nil,意思是“没有Player实例”。当这种情况发生的时候,playerOne变量对Player实例的引用被破坏了。没有其它属性或者变量引用Player实例,因此为了清空它占用的内存从而释放它。在这发生前一步,其析构函数被自动调用,其硬币被返回到银行。

本文部分原文来自于http://www.swiftguide.cn/翻译小组的译文,共同校对中。

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/deinitialization,商业转载请联系我们~ 感谢您对我们工作的支持~

Swift中文教程(14)初始化

转载自letsswift.com

初始化是类,结构体和枚举类型实例化的准备阶段。这个阶段设置这个实例存储的属性的初始化数值和做一些使用实例之前的准备以及必须要做的其他一些设置工作。

通过定义构造器(initializers)实现这个实例化过程,也就是创建一个新的具体实例的特殊方法。和Objective-C不一样的是,Swift的构造器没有返回值。它们主要充当的角色是确保这个实例在使用之前能正确的初始化。

类实例也能实现一个析构器(deinitializer),在类实例销毁之前做一些清理工作。更多的关于析构器(deinitializer)的内容可以参考Deinitialization。

1、存储属性的初始化
类和结构体必须在它们被创建时把它们所有的属性设置为合理的值。存储属性不能为不确定状态

你可以在构造方法里面给一个属性设置一个初始值,或者在定义的时候给属性设置一个默认值,这个行为将会在接下来的章节描述。
注意:当你对给一个属性分配一个默认值的时候,它会调用它相对应的初始化方法,这个值是对属性直接设置的,不会通知它对应的观察者

构造器
构造器是创建一个具体类型实例的方法。最简单的构造器就是一个没有任何参数实例方法,写作init。

在下面的例子定义了一个叫Fahrenheit(华氏度)的新结构体,来储存转换成华氏度的温度。Fahrenheit结构体,有一个属性,叫temperature(温度),它的类型为Double(双精度浮点数):

123456789
structFahrenheit{
    var temperature:Double
    init(){
        temperature =32.0
    }}var f =Fahrenheit()
println("The default temperature is \(f.temperature)° Fahrenheit")// prints "The default temperature is 32.0° Fahrenheit"

这个结构体定义了一个单一的构造方法init,它没有任何参数,它储存的温度属性初始化为32.0度。(水在华氏度的温度情况下的冰点)。
属性的默认值
如上所述,你可以在构造器中设置它自己的储存属性的初始化值。或者在属性声明时,指定属性的默认值,你指定一个默认的属性值,会被分配到它定义的初始值。
注意:如果一个属性常常使用同样的初始化值 ,提供一个默认值会比在初始化使用一个默认值会更好。
同样的结果,但是默认值与属性的初始化在它定义地时候就紧紧地捆绑在一起。很简单地就能构造器更简洁,和可以让你从默认值中推断出这个属性的类型。默认值也能让你优化默认构造器和继承构造器变得更容易,在本章会稍候描述。

你可以在上面的Fahrenheit(华氏度)结构体定义时,给temperature(温度)属性提供默认值。

123
structFahrenheit{
    var temperature =32.0}

2、自定义初始化(Customizing Initialization)
你可以根据输入的参数来自定义初始化过程和可选的属性类型,或者在初始化的时候修改静态属性。在这章节将会详细叙述。

初始化参数
你可以在构造器定义的时候提供一部分参数,在自定义初始化过程中定义变量的类型和名称。
初始化参和函数或者方法参数一样有着同样的功能。

在下面的例子中,定义了一个结构体Celsius。储存了转换成摄氏度的温度,Celsius结构体实现了从不同的温度初始化结构体的两个方法,init(fromFahrenheit:) 和init(fromKelvin:)。

12345678910111213
structCelsius{
    var temperatureInCelsius:Double=0.0
    init(fromFahrenheit fahrenheit:Double){
        temperatureInCelsius =(fahrenheit -32.0)/1.8
    }
    init(fromKelvin kelvin:Double){
        temperatureInCelsius = kelvin -273.15
    }}
let boilingPointOfWater =Celsius(fromFahrenheit:212.0)// boilingPointOfWater.temperatureInCelsius is 100.0
let freezingPointOfWater =Celsius(fromKelvin:273.15)// freezingPointOfWater.temperatureInCelsius is 0.0

第一个构造器只有一个初始化参数,形参(External Parameter Names)fromFahrenheit,和实参(Local Parameter Names)fahrenheit。第二个构造器有一个单一的初始化参数,形参(External Parameter Names)fromKenvin,和实参(Local Parameter Names)kelvin。两个构造器都把单一的参数转换为摄氏度和储存到一个temperatureInCelsius的属性.

实参名(Local Parameter Names)和形参名(External Parameter Names)
和函数参数和方法参数一样,初始化参数拥有在构造器函数体使用的实参,和在调用时使用的形参.
然而,和函数或者方法不同,构造器在圆括号前面没有一个识别函数名称。因此,构造器参数的名称和类型,在被调用的时候,很大程度上扮演一个被识别的重要角色。为此,在构造器中,当你没有提供形参名时,Swift就会为每一个参数提供一个自动的形参名。这个形参名和实参名相同,就像和之前你写的每一个初始化参数的hash符号一样。
注意:如果你在构造器中没有定义形参,提供一个下横线(_)作为区分形参和上面说描述的重写默认行为。

在下面的例子 ,定义了一个结构体Color,拥有三个静态属性red,green和blue。这些属性储存了从0.0到1.0的值,这些值代表红色 ,绿色和蓝色的深度。
Color提供了一个构造器,以及三个双精度(Double)类型的参数:

12345678
structColor{
    let red =0.0, green =0.0, blue =0.0
    init(red:Double, green:Double, blue:Double){
        self.red   = red
        self.green = green
        self.blue  = blue
    }}

无论什么时候,你创建一个Color实例,你必须使用每一个颜色的形参来调用构造器:

1
let magenta =Color(red:1.0, green:0.0, blue:1.0)

值得注意的是,不能不通过形参名来调用构造器。在构造器定义之后,形参名必须一致使用。如果漏掉就会在编写时提示错误。

12
let veryGreen =Color(0.0,1.0,0.0)// this reports a compile-time error - external names are required

可选类型
如果你储存属性使用的是自定义的类型在逻辑上允许值为空-或者他们的值并不在构造器中初始化,或者他们被允许为空。可以定义一个可选类型的属性。可选类型属性是一个自动初始化值为nil,表示这个属性有意在构造器中设置为“空值”(no value yet)。
在下面的例子中,定义了一个SurveryQuestion类,拥有一个可选的String属性response。

这个回答在他们调查问题在发布之前是无法知道的,所以response定义为类型String? ,或者叫可选String(optional String)。说明它会被自动分配一个默认值nil,意思为当surverQuestion初始化时还不存在。

在初始化时修改静态属性
当你在设置静态属性值时,只要在初始化完成之前,你都可以在初始化时随时修改静态属性。
注意:对于类的实例化,一个静态属性只能在初始化时被修改,这个初始化在类定义时已经确定。

你可以重写SurveryQuestion例子,对于问题的text属性,使用静态属性会比动态属性要好,因为SurveyQuestion实例被创建之后就无法修改。尽管text属性现在是静态的,但是仍然可以在构造器中被设置:

1234567891011121314
classSurveyQuestion{
    let text:String
    var response:String?
    init(text:String){
        self.text = text
    }
    func ask(){
        println(text)
    }}
let beetsQuestion =SurveyQuestion(text:"How about beets?")
beetsQuestion.ask()// prints "How about beets?"
beetsQuestion.response ="I also like beets. (But not with cheese.)"

3、默认构造器

Swift为每一个结构或者基类提供了默认的构造器,来初始化它们所包含的所有属性。默认构造器将会创建一个新的实例然后将它们的属性设置为默认值。

下面的例子定义了一个叫ShoppingListItem的类,包含了名称,数量和是否已购买的属性,将会被用在购物清单中:

123456
classShoppingListItem{
    var name:String?
    var quantity =1
    var purchased =false}var item =ShoppingListItem()

因为ShoppingListItem类中所有的属性都有默认值,并且这个类是一个没有父类的基类,所以它默认拥有一个会将所有包含的属性设置为初始值的默认构造器。比如在这个例子中name属性是一个可选String属性,它会被默认设置为nil,尽管在代码中没有指明。上面的例子使用默认构造器创建了一个ShoppingListItem类,记做ShoppingListItem(),然后将它赋值给了变量item。

结构类型的成员逐一构造器

除了上面提到的默认构造器之外,结构类型还有另外一种成员逐一完成初始化的构造器,可以在定义结构的时候直接指定每个属性的初始值。

成员逐一构造器是一种为结构的成员属性进行初始化的简便方法。下面的例子定义了一个叫Size的结构,和两个属性分别叫width和height。每个属性都是Double类型的并且被初始化为0.0。

因为每个存储属性都有默认值,在Size结构创建一个实例的时候就可以自动调用这个成员逐一构造器init(width:height:):

1234
structSize{
    var width =0.0, height =0.0}
let twoByTwo =Size(width:2.0, height:2.0)

4、数值类型的构造器代理

在实例的初始化过程中,构造器可以调用其他的构造器来完成初始化。这个过程叫构造器代理,可以避免多个构造器的重复代码。

对于数值类型和类来说,构造器代理的工作形式是不一样的。数值类型(结构和枚举)不支持继承,因此他们的构造器代理相对简单,因为它们只能使用自己的构造器代理。但是一个类可以继承自另外一个类,所以类需要确保在初始化的时候将它所有的存储属性都设置为正确的值。这种过程在下一节类的继承和初始化中叙述。

对于数值类型来说,可以使用self.init来调用其他构造器,注意只能在这个数值类型内部调用相应的构造器。

需要注意的是如果你为数值类型定义了一个构造器,你就不能再使用默认构造器了。这种特性可以避免当你提供了一个特别复杂的构造器的时候,另外一个人误使用了默认构造器而出错。

注意:如果你想要同时使用默认构造器和你自己设置的构造器,不要将这两种构造器写在一起,而是使用扩展形式。更多内容可以参考Extensions一章。

下面的示例定义了一个结构Rect来表示一个几何中的矩形。这个Rect结构需要另外两个结构来组成,包括Size和Point,初始值均为0.0:

123456
structSize{
    var width =0.0, height =0.0}structPoint{
    var x =0.0, y =0.0}

现在你有三种初始化Rect结构的方式:直接使用为origin和size属性初始化的0值,给定一个指定的origin和size,或者使用中心点和大小来初始化。下面的例子包含了这三种初始化方式:

1234567891011121314
structRect{
    var origin =Point()
    var size =Size()
    init(){}
    init(origin:Point, size:Size){
        self.origin = origin
        self.size = size
    }
    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)
    }}

init()构造器和默认构造器的功能相同。这个构造器不需要任何内容,只是用来在已有其他构造器的时候表示默认构造器的依然存在。调用这个构造器创建的Rect,根据Point和Size的结构定义,Point(x: 0.0, y: 0.0) ,Size(width: 0.0, height: 0.0) origin和size都会被设置为0。

12
let basicRect =Rect()// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)

第二个Rect构造器init(origin:size:)和成员逐一构造器类似,它使用给定的值来初始化结构的属性:

123
let originRect =Rect(origin:Point(x:2.0, y:2.0),
    size:Size(width:5.0, height:5.0))// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)

第三个构造器init(center:size)就更加复杂一些,它首先使用center和size计算出了origin的值,然后调用(或者是使用代理)了init(origin:size)构造器,设置origin和size的值:

123
let centerRect =Rect(center:Point(x:4.0, y:4.0),
    size:Size(width:3.0, height:3.0))// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

init(center:size:)构造器同样可以设置oringin和size的值,而且使用起来也非常方便,代码也比较简洁因为它使用了已有的一些构造器。

注意:可以参考Extensions一章,学习怎样省略init()和init(origin:size:)

5、类的继承和初始化

译者注:本小节内容Apple从底层解释,十分复杂,建议有需要的读者自行阅读英文原文。

本小节主要的意思就是说:

1、自定义初始化方法要先调用自己类默认初始化方法,自己重写默认初始化方法要先调用父类默认初始化方法

2、应该要先调用父类的构造器或者自身的默认构造器,以防止先给属性赋值了然后才调用父类或者自身的默认构造器把以前的赋值覆盖了

一个类的所有存储属性-包括从父类继承而来的属性-都必须在初始化的时候设置初始值。

Swift为class类型定义了两种构造器来确保它们所有的存储属性都设置了初始值。这两种方式叫做指定构造器和便捷构造器。

指定构造器和便捷构造器

指定构造器是一个类最主要的构造器。指定构造器通过设置所有属性的初值并且调用所有的父类构造器来根据构造链一次初始化所有的属性。

类所拥有的指定构造器很少,一般只有一个,并且是连接这父类的构造链依次完成构造的。

每个类至少有一个指定构造器,在有些情况下,需要使用继承来从父类中得到该指定构造器,更多内容可以查看后面的Automatic Initializer Inheritance章节。

便捷构造器是类的第二种常用构造器。你可以调用同一个类中的指定构造器来定义一个便捷构造器,使用指定构造器来设置相关的参数默认值。你还可以定义一个便捷构造器来创建这个类的实例或者是别的特殊用途。

如果你的类不需要它们,也可以不定义便捷构造器。不过对于常见初始化模型需要快捷方式的时候创建一个便捷构造器可以让你的初始化过程变成十分简单便捷。

构造链

为了简化指定构造器和便捷构造器的关系,Swift为两种构造器的代理调用设置了三个规则:

规则1

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

规则2

便捷构造器只能调用同一个类中的其它构造器

规则3

便捷构造器必须以调用一个指定构造器结束

记下这些规则的简单方法是:

指定构造器必须向上代理

便捷构造器必须横向代理

可以使用下面的图来表示:

image

父类中的两个便捷构造器依次调用直到指定构造器,子类中的指定构造器调用了父类的指定构造器。

注意:这些规则不会影响每个类的实例创建过程。每个构造器都可以用来创建它们各自的类的实例。这些规则只影响你如何编写类实现代码。

下图演示的是另一种更为复杂的具有四个等级的类。这个图展示了指定构造器在类的初始化过程中如何被作为“漏斗”节点的。这个构造链简化了类与类之间的交互关系:

image

两阶段的初始化

在Swift中,类的初始化要经过两个阶段。在第一个阶段,每一个存储属性都被设置了一个初始值。一旦每个存储属性的值在初始化阶段被设置了,在第二个阶段,每个类在这个实例被使用之前都会有机会来设置它们相应的存储属性。

两阶段的模式使初始化过程更加安全,还可以让每个类在类的层级关系中具有更多的可能性。两阶段初始化方法可以防止属性在被初始化之前就被使用,或者是被另一个构造器错误地赋值。

注意:Swift的这种两阶段初始化方法跟Objective-C中的类似。主要的差别是在第一个过程中,Objective-C为每个属性赋值0或者null,而在Swift中,可以个性化设置这些初始值,还可以处理一些初始值不能是0或者nil的情况。

Swift编译器通过四重检查来确保两阶段式的初始化过程是完全正确无误的:

Safety check 1
A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.

As mentioned above, the memory for an object is only considered fully initialized once the initial state of all of its stored properties is known. In order for this rule to be satisfied, a designated initializer must make sure that all its own properties are initialized before it hands off up the chain.

Safety check 2
A designated initializer must delegate up to a superclass initializer before assigning a value to an inherited property. If it doesn’t, the new value the designated initializer assigns will be overwritten by the superclass as part of its own initialization.
Safety check 3
A convenience initializer must delegate to another initializer before assigning a value to any property (including properties defined by the same class). If it doesn’t, the new value the convenience initializer assigns will be overwritten by its own class’s designated initializer.
Safety check 4
An initializer cannot call any instance methods, read the values of any instance properties, or refer to selfas a value until after the first phase of initialization is complete.

The class instance is not fully valid until the first phase ends. Properties can only be accessed, and methods can only be called, once the class instance is known to be valid at the end of the first phase.

Here’s how two-phase initialization plays out, based on the four safety checks above:

Phase 1

  • A designated or convenience initializer is called on a class.
  • Memory for a new instance of that class is allocated. The memory is not yet initialized.
  • A designated initializer for that class confirms that all stored properties introduced by that class have a value. The memory for these stored properties is now initialized.
  • The designated initializer hands off to a superclass initializer to perform the same task for its own stored properties.
  • This continues up the class inheritance chain until the top of the chain is reached.
  • Once the top of the chain is reached, and the final class in the chain has ensured that all of its stored properties have a value, the instance’s memory is considered to be fully initialized, and phase 1 is complete.

Phase 2

  • Working back down from the top of the chain, each designated initializer in the chain has the option to customize the instance further. Initializers are now able to access self and can modify its properties, call its instance methods, and so on.
  • Finally, any convenience initializers in the chain have the option to customize the instance and to work with self.

Here’s how phase 1 looks for an initialization call for a hypothetical subclass and superclass:

image

In this example, initialization begins with a call to a convenience initializer on the subclass. This convenience initializer cannot yet modify any properties. It delegates across to a designated initializer from the same class.

The designated initializer makes sure that all of the subclass’s properties have a value, as per safety check 1. It then calls a designated initializer on its superclass to continue the initialization up the chain.

The superclass’s designated initializer makes sure that all of the superclass properties have a value. There are no further superclasses to initialize, and so no further delegation is needed.

As soon as all properties of the superclass have an initial value, its memory is considered fully initialized, and Phase 1 is complete.

Here’s how phase 2 looks for the same initialization call:

image

The superclass’s designated initializer now has an opportunity to customize the instance further (although it does not have to).

Once the superclass’s designated initializer is finished, the subclass’s designated initializer can perform additional customization (although again, it does not have to).

Finally, once the subclass’s designated initializer is finished, the convenience initializer that was originally called can perform additional customization.

构造器的继承和重写

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

If you want your custom subclass to present one or more of the same initializers as its superclass—perhaps to perform some customization during initialization—you can provide an overriding implementation of the same initializer within your custom subclass.

If the initializer you are overriding is a designated initializer, you can override its implementation in your subclass and call the superclass version of the initializer from within your overriding version.

If the initializer you are overriding is a convenience initializer, your override must call another designated initializer from its own subclass, as per the rules described above in Initializer Chaining.

NOTE

Unlike methods, properties, and subscripts, you do not need to write the override keyword when overriding an initializer.

构造器自动继承

As mentioned above, subclasses do not not inherit their superclass initializers by default. However, superclass initializers are automatically inherited if certain conditions are met. In practice, this means that you do not need to write initializer overrides in many common scenarios, and can inherit your superclass initializers with minimal effort whenever it is safe to do so.

Assuming that you provide default values for any new properties you introduce in a subclass, the following two rules apply:

Rule 1
If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
Rule 2
If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.

These rules apply even if your subclass adds further convenience initializers.

NOTE

A subclass can implement a superclass designated initializer as a subclass convenience initializer as part of satisfying rule 2.

指定初始化和便捷初始化的语法

Designated initializers for classes are written in the same way as simple initializers for value types:

123
init(parameters){
    statements
}

Convenience initializers are written in the same style, but with the convenience keyword placed before theinitkeyword, separated by a space:

123
convenience init(parameters){
    statements
}

指定初始化和便捷初始化实战

下面的例子演示的是指定构造器,便捷构造器和自动构造器继承的实战。例子中定义了三个类分别叫Food,RecipeIngredient和ShoppingListItem,并给出了他们的继承关系。

基类叫做Food,是一个简单的类只有一个name属性:

123456789
classFood{
    var name:String
    init(name:String){
        self.name = name
    }
    convenience init(){
        self.init(name:"[Unnamed]")
    }}

下图就是Food类的构造链:

image

类不存在成员逐一构造器,所以Food类提供了一个指定构造器,使用参数name来完成初始化:

12
let namedMeat =Food(name:"Bacon")// namedMeat's name is "Bacon"

init(name:String)构造器就是Food类中的指定构造器,因为它保证了每一个Food实例的属性都被初始化了。由于它没有父类,所以不需要调用super.init()构造器。

Food类也提供了便捷构造器init(),这个构造器没有参数,仅仅只是将name设置为了[Unnamed]:

12
let mysteryMeat =Food()// mysteryMeat's name is "[Unnamed]"

下一个类是Food的子类,叫做RecipeIngredient。这个类描述的是做饭时候的配料,包括一个数量属性Int类型,然后定义了两个构造器:

12345678910
classRecipeIngredient:Food{
    var quantity:Int
    init(name:String, quantity:Int){
        self.quantity = quantity
        super.init(name: name)
    }
    convenience init(name:String){
        self.init(name: name, quantity:1)
    }}

下图表示这两个类的构造链:

image

RecipeIngredient类有它自己的指定构造器init(name: String, quantity:Int),用来创建一个新的RecipeIngredient实例。在这个指定构造器中它调用了父类的指定构造器init(name:String)。

然后它还有一个便捷构造器,init(name),它使用了同一个类中的指定构造器。当然它还包括一个继承来的默认构造器init(),这个构造器将使用RecipeIngredient中的init(name: String)构造器。

RecipeIngredient also defines a convenience initializer, init(name: String), which is used to create aRecipeIngredient instance by name alone. This convenience initializer assumes a quantity of 1 for anyRecipeIngredient instance that is created without an explicit quantity. The definition of this convenience initializer makes RecipeIngredient instances quicker and more convenient to create, and avoids code duplication when creating several single-quantity RecipeIngredient instances. This convenience initializer simply delegates across to the class’s designated initializer.

Note that the init(name: String) convenience initializer provided by RecipeIngredient takes the same parameters as the init(name: String) designated initializer from Food. Even though RecipeIngredient provides this initializer as a convenience initializer, RecipeIngredient has nonetheless provided an implementation of all of its superclass’s designated initializers. Therefore, RecipeIngredient automatically inherits all of its superclass’s convenience initializers too.

In this example, the superclass for RecipeIngredient is Food, which has a single convenience initializer calledinit(). This initializer is therefore inherited by RecipeIngredient. The inherited version of init() functions in exactly the same way as the Food version, except that it delegates to the RecipeIngredient version ofinit(name: String) rather than the Food version.

上述三种构造器都可以用来创建RecipeIngredient实例:

123
let oneMysteryItem =RecipeIngredient()
let oneBacon =RecipeIngredient(name:"Bacon")
let sixEggs =RecipeIngredient(name:"Eggs", quantity:6)

最后一个类是ShoppingListItem继承自RecipeIngredient,它又包括了另外两个属性,是否已购买purchased,描述description,描述本身还是一个计算属性:

12345678
classShoppingListItem:RecipeIngredient{
    var purchased =false
    var description:String{
    var output ="\(quantity) x \(name.lowercaseString)"
        output += purchased ?" yes":" no"
        return output
    }}

注意:ShoppingListItem没有定义构造器来初始化purchased的值,因为每个商品在买之前purchased都是默认被设置为没有被购买的。

因为ShoppingListItem没有提供其他构造器,那么它就完全继承了父类的构造器,用下图可以说明:

image

你可以在创建ShoppingListItem实例时使用所有的继承构造器:

12345678910111213
var breakfastList =[
    ShoppingListItem(),
    ShoppingListItem(name:"Bacon"),
    ShoppingListItem(name:"Eggs", quantity:6),]
breakfastList[0].name ="Orange juice"
breakfastList[0].purchased =truefor item in breakfastList {
    println(item.description)}// 1 x orange juice yes// 1 x bacon no// 6 x eggs no

通过输出可以看出所有的实例在创建的时候,属性的默认值都被正确的初始化了。

6、通过闭包或者函数来设置一个默认属性值

如果存储属性的默认值需要额外的特殊设置,可以使用闭包或者函数来完成。

闭包或者函数会创建一个临时变量来作为返回值为这个属性赋值。下面是如果使用闭包赋值的一个示意代码:

1234567
classSomeClass{
    let someProperty:SomeType={
        // create a default value for someProperty inside this closure
        // someValue must be of the same type as SomeType
        return someValue
        }()}

需要注意的是在闭包结尾有两个小括号,告诉Swift这个闭包是需要立即执行的。

注意:如果你时候闭包来初始化一个属性,在闭包执行的时候,后续的一些属性还没有被初始化。在闭包中不要访问任何后面的属性,一面发生错误,也不能使用self属性,或者其它实例方法。

下面的例子是一个叫Checkerboard的结构,是由游戏Checkers来的

image这个游戏是在一个10×10的黑白相间的格子上进行的。来表示这个游戏盘,使用了一个叫Checkerboard的结构,其中一个属性叫boardColors,是一个100个Bool类型的数组。true表示这个格子是黑色,false表示是白色。那么在初始化的时候可以通过下面的代码来初始化:

1234567891011121314151617
structCheckerboard{
    let boardColors:Bool[]={
        var temporaryBoard =Bool[]()
        var isBlack =false
        for i in1...10{
            for j in1...10{
                temporaryBoard.append(isBlack)
                isBlack =!isBlack
            }
            isBlack =!isBlack
        }
        return temporaryBoard
        }()
    func squareIsBlackAtRow(row:Int, column:Int)->Bool{
        return boardColors[(row *10)+ column]
    }}

当一个新的Checkerboard实例创建的时候,闭包会执行,然后boardColor的默认值将会被依次计算并且返回,然后作为结构的一个属性。通过使用squareIsBlackAtRow工具函数可以检测是否被正确设置:

12345
let board =Checkerboard()
println(board.squareIsBlackAtRow(0, column:1))// prints "true"
println(board.squareIsBlackAtRow(9, column:9))// prints "false"

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/initialization,商业转载请联系我们~ 感谢您对我们工作的支持~