结构体是Swift中非常重要的对象类型。枚举的case数量固定,实际上它是一种精简、特殊的对象。类则是另一个极端,它的能力过于强大;它拥有一些结构体缺乏的特性,不过如果不需要这些特性,那么结构体就是最佳选择。
在Swift头文件所声明的大量对象类型中,只有4个是类(你不大可能用到它们)。与之相反,Swift本身提供的几乎所有内建对象类型都是结构体。String是个结构体、Int是个结构体、Range是个结构体、Array也是结构体。这表明了结构体的强大功能。
4.3.1 结构体初始化器、属性与方法
如果一个结构体没有显式初始化器,或不需要显式初始化器(因为结构体没有存储属性,或在声明中为所有存储属性都赋予了默认值),那么它就会自动拥有一个不带参数的隐式初始化器init()。比如:
struct Digit { var number = 42}
可以通过调用Digit()来初始化上面的结构体。不过,如果添加了自定义的显式初始化器,那么隐式初始化器就不复存在了:
struct Digit { var number = 42 init(number:Int) { self.number = number }}
现在可以调用Digit(number:42),不过不能再调用Digit()了。当然了,你可以添加一个显式初始化器完成相同的事情:
struct Digit { var number = 42 init {} init(number:Int) { self.number = number }}
现在又可以调用Digit()了,也可以调用Digit(number:42)。
拥有存储属性,并且没有显式初始化器的结构体会自动获得来自于实例属性的隐式初始化器。这叫作成员初始化器。比如:
struct Digit { var number : Int // can use /"let/" here}
上述结构体是合法的(事实上,即便使用let而非var来声明number属性,它也是合法的),不过似乎我们并没有实现在声明中或初始化器中初始化所有存储属性的契约。原因在于该结构体自动具有了一个成员初始化器,它会初始化所有属性。在该示例中,成员初始化器叫作init(number:)。
即便在声明中为可变存储属性赋了默认值,成员初始化器也是存在的;这样,除了隐式init()初始化器,该结构体还有一个成员初始化器init(number:):
struct Digit { var number = 42}
如果添加了自定义的显式初始化器,那么成员初始化器就不复存在了(当然,你还是可以提供显式初始化器完成相同的事情)。
如果结构体拥有显式初始化器,那么它必须要实现这个契约:要么在声明中,要么在所有初始化器中完成对所有存储属性的初始化。如果结构体有多个显式初始化器,那么可以通过调用self.init(...)进行委托。
结构体可以拥有实例属性与静态属性,它们既可以是存储变量,也可以是计算变量。如果其他代码想要设置结构体实例的某个属性,那么对该实例的引用就必须是个变量(var)而不能是常量(let)。
结构体可以拥有实例方法(包括下标)与静态方法。如果实例方法设置某个属性,那么必须要将其标记为mutating,调用者对该结构体实例的引用必须是个变量(var)而不能是常量(let)。mutating实例方法甚至可以用别的实例替换掉当前实例,只需将self设置为相同结构体的不同实例即可(下标Setter总是mutating的,因此不必显式标记)。
4.3.2 将结构体作为命名空间
我经常将退化的结构体作为常量的命名空间。之所以称一个结构体为“退化的”,是因为它只由静态成员构成;我不会通过该对象类型创建任何实例。不过,这么使用结构体是完全没问题的。
比如,假设要在Cocoa的NSUserDefaults中存储用户偏好信息。NSUserDefaults是一种字典:每一项都可以通过键来访问,键通常是字符串。一个常见的程序错误就是在每次需要键时都手工写出这些字符串键;如果拼错了键名,那么在编译期是不会有任何错误出现的,不过代码将无法正常工作。好的方式是将这些键作为常量字符串,并使用字符串的名字;通过这种方式,如果在输入字符串名的时候出错了,那么编译器会提醒你。拥有静态成员的结构体非常适合定义这些常量字符串,并且将这些名字形成到一个命名空间中:
struct Default { static let Rows = /"CardMatrixRows/" static let Columns = /"CardMatrixColumns/" static let HazyStripy = /"HazyStripy/"}
上述代码表示我现在可以通过一个名字来引用NSUserDefaults键,如Default.HazyStripy。
如果结构体声明了静态成员,其值是相同结构体类型的实例,那么在需要该结构体类型实例的情况下,提供结构体静态成员时就可以省略结构体的名字,就好像该结构体是个枚举一样:
struct Thing { var rawValue : Int = 0 static var One : Thing = Thing(rawValue:1) static var Two : Thing = Thing(rawValue:2)}let thing : Thing = .One // no need to say Thing.One here
示例本身是我们设想的,不过使用场景却不是;很多Objective-C枚举都是通过这种结构体桥接到Swift的(后面还会对此进行介绍)。