iflet roomCount = john.residence?.numberOfRooms { print("John's residence has \(roomCount) room(s).") } else { print("Unable to retrieve the number of rooms.") } // 打印“Unable to retrieve the number of rooms.”
iflet roomCount = john.residence?.numberOfRooms { print("John's residence has \(roomCount) room(s).") } else { print("Unable to retrieve the number of rooms.") } // 打印“John's residence has 1 room(s).”
classResidence { var rooms = [Room]() var numberOfRooms: Int { return rooms.count } subscript(i: Int) -> Room { get { return rooms[i] } set { rooms[i] = newValue } } funcprintNumberOfRooms() { print("The number of rooms is \(numberOfRooms)") } var address: Address? }
使用前面定义过的类,创建一个 Person 实例,然后像之前一样,尝试访问 numberOfRooms 属性:
1 2 3 4 5 6 7
let john =Person() iflet roomCount = john.residence?.numberOfRooms { print("John's residence has \(roomCount) room(s).") } else { print("Unable to retrieve the number of rooms.") } // 打印“Unable to retrieve the number of rooms.”
上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建 Address 实例,然后将该实例返回用于赋值。该函数会在返回前打印“Function was called”,这使你能验证等号右侧的代码是否被执行。
1 2 3 4 5 6 7 8 9 10
funccreateAddress() -> Address { print("Function was called.")
let someAddress =Address() someAddress.buildingNumber ="29" someAddress.street ="Acacia Road"
如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是 Void?,而不是 Void,因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用 if 语句来判断能否成功调用 printNumberOfRooms() 方法,即使方法本身没有定义返回值。通过判断返回值是否为 nil 可以判断调用是否成功:
1 2 3 4 5 6
if john.residence?.printNumberOfRooms() !=nil { print("It was possible to print the number of rooms.") } else { print("It was not possible to print the number of rooms.") } // 打印“It was not possible to print the number of rooms.”
if (john.residence?.address = someAddress) !=nil { print("It was possible to set the address.") } else { print("It was not possible to set the address.") } // 打印“It was not possible to set the address.”
iflet firstRoomName = john.residence?[0].name { print("The first room name is \(firstRoomName).") } else { print("Unable to retrieve the first room name.") } // 打印“Unable to retrieve the first room name.”
iflet firstRoomName = john.residence?[0].name { print("The first room name is \(firstRoomName).") } else { print("Unable to retrieve the first room name.") } // 打印“The first room name is Living Room.”
访问可选类型的下标
如果下标返回可选类型值,比如 Swift 中 Dictionary 类型的键的下标,可以在下标的结尾括号后面放一个问号来在其可选返回值上进行可选链式调用:
下面的例子尝试访问 john 中的 residence 属性中的 address 属性中的 street 属性。这里使用了两层可选链式调用,residence 以及 address 都是可选值:
1 2 3 4 5 6
iflet johnsStreet = john.residence?.address?.street { print("John's street name is \(johnsStreet).") } else { print("Unable to retrieve the address.") } // 打印“Unable to retrieve the address.”
iflet johnsStreet = john.residence?.address?.street { print("John's street name is \(johnsStreet).") } else { print("Unable to retrieve the address.") } // 打印“John's street name is Laurel Street.”
iflet buildingIdentifier = john.residence?.address?.buildingIdentifier() { print("John's building identifier is \(buildingIdentifier).") } // 打印“John's building identifier is The Larches.”
如果要在该方法的返回值上进行可选链式调用,在方法的圆括号后面加上问号即可:
1 2 3 4 5 6 7 8 9
iflet beginsWithThe = john.residence?.address?.buildingIdentifier()?.hasPrefix("The") { if beginsWithThe { print("John's building identifier begins with \"The\".") } else { print("John's building identifier does not begin with \"The\".") } } // 打印“John's building identifier begins with "The".”
这是一个析构器实践的例子。这个例子描述了一个简单的游戏,这里定义了两种新类型,分别是 Bank 和 Player。Bank 类管理一种虚拟硬币,确保流通的硬币数量永远不可能超过 10,000。在游戏中有且只能有一个 Bank 存在,因此 Bank 用类来实现,并使用类型属性和类型方法来存储和管理其当前状态。
每个 Player 实例在初始化的过程中,都从 Bank 对象获取指定数量的硬币。如果没有足够的硬币可用,Player 实例可能会收到比指定数量少的硬币。
Player 类定义了一个 win(coins:) 方法,该方法从 Bank 对象获取一定数量的硬币,并把它们添加到玩家的钱包。Player 类还实现了一个析构器,这个析构器在 Player 实例释放前被调用。在这里,析构器的作用只是将玩家的所有硬币都返还给 Bank 对象:
1 2 3 4 5
var playerOne: Player? =Player(coins: 100) print("A new player has joined the game with \(playerOne!.coinsInPurse) coins") // 打印“A new player has joined the game with 100 coins” print("There are now \(Bank.coinsInBank) coins left in the bank") // 打印“There are now 9900 coins left in the bank”
创建一个 Player 实例的时候,会向 Bank 对象申请得到 100 个硬币,前提是有足够的硬币可用。这个 Player 实例存储在一个名为 playerOne 的可选类型的变量中。这里使用了一个可选类型的变量,是因为玩家可以随时离开游戏,设置为可选使你可以追踪玩家当前是否在游戏中。
playerOne!.win(coins: 2_000) print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins") // 打印“PlayerOne won 2000 coins & now has 2100 coins” print("The bank now only has \(Bank.coinsInBank) coins left") // 打印“The bank now only has 7900 coins left”
在这里,玩家已经赢得了 2,000 枚硬币,所以玩家的钱包中现在有 2,100 枚硬币,而 Bank 对象只剩余 7,900 枚硬币。
1 2 3 4 5
playerOne =nil print("PlayerOne has left the game") // 打印“PlayerOne has left the game” print("The bank now has \(Bank.coinsInBank) coins") // 打印“The bank now has 10000 coins”
玩家现在已经离开了游戏。这通过将可选类型的 playerOne 变量设置为 nil 来表示,意味着“没有 Player 实例”。当这一切发生时,playerOne 变量对 Player 实例的引用被破坏了。没有其它属性或者变量引用 Player 实例,因此该实例会被释放,以便回收内存。在这之前,该实例的析构器被自动调用,玩家的硬币被返还给银行。
structFahrenheit { var temperature: Double init() { temperature =32.0 } } var f =Fahrenheit() print("The default temperature is \(f.temperature)° Fahrenheit") // 打印“The default temperature is 32.0° Fahrenheit”
这个结构体定义了一个不带形参的构造器 init,并在里面将存储型属性 temperature 的值初始化为 32.0(华氏温度下水的冰点)。
Color 提供了一个构造器,为红蓝绿提供三个合适 Double 类型的形参命名。Color 也提供了第二个构造器,它只包含名为 white 的 Double 类型的形参,它为三个颜色的属性提供相同的值。
1 2 3 4 5 6 7 8 9 10 11 12 13
structColor { let red, green, blue: Double init(red: Double, green: Double, blue: Double) { self.red = red self.green = green self.blue = blue } init(white: Double) { red = white green = white blue = white } }
两种构造器都能通过为每一个构造器形参提供命名值来创建一个新的 Color 实例:
1 2
let magenta =Color(red: 1.0, green: 0.0, blue: 1.0) let halfGray =Color(white: 0.5)
classSurveyQuestion { var text: String var response: String? init(text: String) { self.text = text } funcask() { print(text) } }
let cheeseQuestion =SurveyQuestion(text: "Do you like cheese?") cheeseQuestion.ask() // 打印“Do you like cheese?” cheeseQuestion.response ="Yes, I do like cheese."
你可以修改上面的 SurveyQuestion 示例,用常量属性替代变量属性 text,表示问题内容 text 在 SurveyQuestion 的实例被创建之后不会再被修改。尽管 text 属性现在是常量,我们仍然可以在类的构造器中设置它的值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classSurveyQuestion { let text: String var response: String? init(text: String) { self.text = text } funcask() { print(text) } } let beetsQuestion =SurveyQuestion(text: "How about beets?") beetsQuestion.ask() // 打印“How about beets?” beetsQuestion.response ="I also like beets. (But not with cheese.)"
默认构造器
如果结构体或类为所有属性提供了默认值,又没有提供任何自定义的构造器,那么 Swift 会给这些结构体或类提供一个默认构造器。这个默认构造器将简单地创建一个所有属性值都设置为它们默认值的实例。
iflet valueMaintained =Int(exactly: wholeNumber) { print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)") } // 打印“12345.0 conversion to Int maintains value of 12345”
let valueChanged =Int(exactly: pi) // valueChanged 是 Int? 类型,不是 Int 类型
if valueChanged ==nil { print("\(pi) conversion to Int does not maintain value") } // 打印“3.14159 conversion to Int does not maintain value”
下例中,定义了一个名为 Animal 的结构体,其中有一个名为 species 的 String 类型的常量属性。同时该结构体还定义了一个接受一个名为 species 的 String 类型形参的可失败构造器。这个可失败构造器检查传入的species 值是否为一个空字符串。如果为空字符串,则构造失败。否则,species 属性被赋值,构造成功。
1 2 3 4 5 6 7 8 9
structAnimal { let species: String init?(species: String) { if species.isEmpty { returnnil } self.species = species } }
你可以通过该可失败构造器来尝试构建一个 Animal 的实例,并检查构造过程是否成功:
1 2 3 4 5 6 7
let someCreature =Animal(species: "Giraffe") // someCreature 的类型是 Animal? 而不是 Animal
iflet giraffe = someCreature { print("An animal was initialized with a species of \(giraffe.species)") } // 打印“An animal was initialized with a species of Giraffe”
如果你给该可失败构造器传入一个空字符串到形参 species,则会导致构造失败:
1 2 3 4 5 6 7
let anonymousCreature =Animal(species: "") // anonymousCreature 的类型是 Animal?, 而不是 Animal
if anonymousCreature ==nil { print("The anonymous creature could not be initialized") } // 打印“The anonymous creature could not be initialized”
let fahrenheitUnit =TemperatureUnit(symbol: "F") if fahrenheitUnit !=nil { print("This is a defined temperature unit, so initialization succeeded.") } // 打印“This is a defined temperature unit, so initialization succeeded.”
let unknownUnit =TemperatureUnit(symbol: "X") if unknownUnit ==nil { print("This is not a defined temperature unit, so initialization failed.") } // 打印“This is not a defined temperature unit, so initialization failed.”
因此上面的 TemperatureUnit 的例子可以用原始值类型的 Character 和进阶的 init?(rawValue:) 构造器重写为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
enumTemperatureUnit: Character { caseKelvin="K", Celsius="C", Fahrenheit="F" }
let fahrenheitUnit =TemperatureUnit(rawValue: "F") if fahrenheitUnit !=nil { print("This is a defined temperature unit, so initialization succeeded.") } // 打印“This is a defined temperature unit, so initialization succeeded.”
let unknownUnit =TemperatureUnit(rawValue: "X") if unknownUnit ==nil { print("This is not a defined temperature unit, so initialization failed.") } // 打印“This is not a defined temperature unit, so initialization failed.”
classVehicle { var currentSpeed =0.0 var description: String { return"traveling at \(currentSpeed) miles per hour" } funcmakeNoise() { // 什么也不做——因为车辆不一定会有噪音 } }
classTandem: Bicycle { var currentNumberOfPassengers =0 }
Tandem 从 Bicycle 继承了所有的属性与方法,这又使它同时继承了 Vehicle 的所有属性与方法。Tandem 也增加了一个新的叫做 currentNumberOfPassengers 的存储型属性,默认值为 0。
如果你创建了一个 Tandem 的实例,你可以使用它所有的新属性和继承的属性,还能查询从 Vehicle 继承来的只读属性 description:
1 2 3 4 5 6
let tandem =Tandem() tandem.hasBasket =true tandem.currentNumberOfPassengers =2 tandem.currentSpeed =22.0 print("Tandem: \(tandem.description)") // 打印:“Tandem: traveling at 22.0 miles per hour”
let automatic =AutomaticCar() automatic.currentSpeed =35.0 print("AutomaticCar: \(automatic.description)") // 打印“AutomaticCar: traveling at 35.0 miles per hour in gear 4”
防止重写
你可以通过把方法,属性或下标标记为 final 来防止它们被重写,只需要在声明关键字前加上 final 修饰符即可(例如:final var、final func、final class func 以及 final subscript)。
任何试图对带有 final 标记的方法、属性或下标进行重写的代码,都会在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 final。
可以通过在关键字 class 前添加 final 修饰符(final class)来将整个类标记为 final 。这样的类是不可被继承的,试图继承这样的类会导致编译报错。