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 。这样的类是不可被继承的,试图继承这样的类会导致编译报错。
structTimesTable { let multiplier: Int subscript(index: Int) -> Int { return multiplier * index } } let threeTimesTable =TimesTable(multiplier: 3) print("six times three is \(threeTimesTable[6])") // 打印“six times three is 18”
structPoint { var x =0.0, y =0.0 funcisToTheRightOf(x: Double) -> Bool { returnself.x > x } } let somePoint =Point(x: 4.0, y: 5.0) if somePoint.isToTheRightOf(x: 1.0) { print("This point is to the right of the line where x == 1.0") } // 打印“This point is to the right of the line where x == 1.0”
structPoint { var x =0.0, y =0.0 mutatingfuncmoveBy(xdeltaX: Double, ydeltaY: Double) { x += deltaX y += deltaY } } var somePoint =Point(x: 1.0, y: 1.0) somePoint.moveBy(x: 2.0, y: 3.0) print("The point is now at (\(somePoint.x), \(somePoint.y))") // 打印“The point is now at (3.0, 4.0)”
上面的 Point 结构体定义了一个可变方法 moveBy(x:y :) 来移动 Point 实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了 mutating 关键字,从而允许修改属性。
注意,不能在结构体类型的常量(a constant of structure type)上调用可变方法,因为其属性不能被改变,即使属性是变量属性,详情参见 常量结构体的存储属性:
classPlayer { var tracker =LevelTracker() let playerName: String funccomplete(level: Int) { LevelTracker.unlock(level +1) tracker.advance(to: level +1) } init(name: String) { playerName = name } }
Player 类创建一个新的 LevelTracker 实例来监测这个用户的进度。它提供了 complete(level:) 方法,一旦玩家完成某个指定等级就调用它。这个方法为所有玩家解锁下一等级,并且将当前玩家的进度更新为下一等级。(我们忽略了 advance(to:) 返回的布尔值,因为之前调用 LevelTracker.unlock(_:) 时就知道了这个等级已经被解锁了)。
你还可以为一个新的玩家创建一个 Player 的实例,然后看这个玩家完成等级一时发生了什么:
1 2 3 4
var player =Player(name: "Argyrios") player.complete(level: 1) print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)") // 打印“highest unlocked level is now 2”
player =Player(name: "Beto") if player.tracker.advance(to: 6) { print("player is now on level 6") } else { print("level 6 has not yet been unlocked") } // 打印“level 6 has not yet been unlocked”
structFixedLengthRange { var firstValue: Int let length: Int } var rangeOfThreeItems =FixedLengthRange(firstValue: 0, length: 3) // 该区间表示整数 0,1,2 rangeOfThreeItems.firstValue =6 // 该区间现在表示整数 6,7,8
structPoint { var x =0.0, y =0.0 } structSize { var width =0.0, height =0.0 } structRect { var origin =Point() var size =Size() var center: Point { get { let centerX = origin.x + (size.width /2) let centerY = origin.y + (size.height /2) returnPoint(x: centerX, y: centerY) } set(newCenter) { origin.x = newCenter.x - (size.width /2) origin.y = newCenter.y - (size.height /2) } } } var square =Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 10.0)) let initialSquareCenter = square.center square.center =Point(x: 15.0, y: 15.0) print("square.origin is now at (\(square.origin.x), \(square.origin.y))") // 打印“square.origin is now at (10.0, 10.0)”
这个例子定义了 3 个结构体来描述几何形状:
Point 封装了一个 (x, y) 的坐标
Size 封装了一个 width 和一个 height
Rect 表示一个有原点和尺寸的矩形
Rect 也提供了一个名为 center 的计算属性。一个 Rect 的中心点可以从 origin(原点)和 size(大小)算出,所以不需要将中心点以 Point 类型的值来保存。Rect 的计算属性 center 提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。
上述例子中创建了一个名为 square 的 Rect 实例,初始值原点是 (0, 0),宽度高度都是 10。如下图中蓝色正方形所示。
square 的 center 属性可以通过点运算符(square.center)来访问,这会调用该属性的 getter 来获取它的值。跟直接返回已经存在的值不同,getter 实际上通过计算然后返回一个新的 Point 来表示 square 的中心点。如代码所示,它正确返回了中心点 (5, 5)。
center 属性之后被设置了一个新的值 (15, 15),表示向右上方移动正方形到如下图橙色正方形所示的位置。设置属性 center 的值会调用它的 setter 来修改属性 origin 的 x 和 y 的值,从而实现移动正方形到新的位置。
简化 Setter 声明
如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 newValue。下面是使用了简化 setter 声明的 Rect 结构体代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
structAlternativeRect { var origin =Point() var size =Size() var center: Point { get { let centerX = origin.x + (size.width /2) let centerY = origin.y + (size.height /2) returnPoint(x: centerX, y: centerY) } set { origin.x = newValue.x - (size.width /2) origin.y = newValue.y - (size.height /2) } } }
简化 Getter 声明
如果整个 getter 是单一表达式,getter 会隐式地返回这个表达式结果。下面是另一个版本的 Rect 结构体,用到了简化的 getter 和 setter 声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
structCompactRect { var origin =Point() var size =Size() var center: Point { get { Point(x: origin.x + (size.width /2), y: origin.y + (size.height /2)) } set { origin.x = newValue.x - (size.width /2) origin.y = newValue.y - (size.height /2) } } }
每当你定义一个新的结构体或者类时,你都是定义了一个新的 Swift 类型。请使用 UpperCamelCase 这种方式来命名类型(如这里的 SomeClass 和 SomeStructure),以便符合标准 Swift 类型的大写命名风格(如 String,Int 和 Bool)。请使用 lowerCamelCase 这种方式来命名属性和方法(如 framerate 和 incrementCount),以便和类型名区分。
以下是定义结构体和定义类的示例:
1 2 3 4 5 6 7 8 9 10
structResolution { var width =0 var height =0 } classVideoMode { var resolution =Resolution() var interlaced =false var frameRate =0.0 var name: String? }
print("The width of someVideoMode is \(someVideoMode.resolution.width)") // 打印 "The width of someVideoMode is 0"
你也可以使用点语法为可变属性赋值:
1 2 3
someVideoMode.resolution.width =1280 print("The width of someVideoMode is now \(someVideoMode.resolution.width)") // 打印 "The width of someVideoMode is now 1280"
print("cinema is now \(cinema.width) pixels wide") // 打印 "cinema is now 2048 pixels wide"
然而,初始的 hd 实例中 width 属性还是 1920:
1 2
print("hd is still \(hd.width) pixels wide") // 打印 "hd is still 1920 pixels wide"
将 hd 赋值给 cinema 时,hd 中所存储的值会拷贝到新的 cinema 实例中。结果就是两个完全独立的实例包含了相同的数值。由于两者相互独立,因此将 cinema 的 width 修改为 2048 并不会影响 hd 中的 width 的值,如下图所示:
枚举也遵循相同的行为准则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
enumCompassPoint { case north, south, east, west mutatingfuncturnNorth() { self= .north } } var currentDirection =CompassPoint.west let rememberedDirection = currentDirection currentDirection.turnNorth()
print("The current direction is \(currentDirection)") print("The remembered direction is \(rememberedDirection)") // 打印 "The current direction is north" // 打印 "The remembered direction is west"
if tenEighty === alsoTenEighty { print("tenEighty and alsoTenEighty refer to the same VideoMode instance.") } // 打印 "tenEighty and alsoTenEighty refer to the same VideoMode instance."
在 Swift 中,枚举类型是一等(first-class)类型。它们采用了很多在传统上只被类(class)所支持的特性,例如计算属性(computed properties),用于提供枚举值的附加信息,实例方法(instance methods),用于提供和枚举值相关联的功能。枚举也可以定义构造函数(initializers)来提供一个初始值;可以在原始实现的基础上扩展它们的功能;还可以遵循协议(protocols)来提供标准的功能。
directionToHead = .south switch directionToHead { case .north: print("Lots of planets have a north") case .south: print("Watch out for penguins") case .east: print("Where the sun rises") case .west: print("Where the skies are blue") } // 打印“Watch out for penguins”
你可以这样理解这段代码:
“判断 directionToHead 的值。当它等于 .north,打印 “Lots of planets have a north”。当它等于 .south,打印 “Watch out for penguins”。”
let somePlanet =Planet.earth switch somePlanet { case .earth: print("Mostly harmless") default: print("Not a safe place for humans") } // 打印“Mostly harmless”
你可以使用一个 switch 语句来检查不同的条形码类型,和之前使用 Switch 语句来匹配枚举值的例子一样。然而,这一次,关联值可以被提取出来作为 switch 语句的一部分。你可以在 switch 的 case 分支代码中提取每个关联值作为一个常量(用 let 前缀)或者作为一个变量(用 var 前缀)来使用:
1 2 3 4 5 6 7
switch productBarcode { case .upc(let numberSystem, let manufacturer, let product, let check): print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).") case .qrCode(let productCode): print("QR code: \(productCode).") } // 打印“QR code: ABCDEFGHIJKLMNOP.”
如果一个枚举成员的所有关联值都被提取为常量,或者都被提取为变量,为了简洁,你可以只在成员名称前标注一个 let 或者 var:
let positionToFind =11 iflet somePlanet =Planet(rawValue: positionToFind) { switch somePlanet { case .earth: print("Mostly harmless") default: print("Not a safe place for humans") } } else { print("There isn't a planet at position \(positionToFind)") } // 打印“There isn't a planet at position 11”
indirectenumArithmeticExpression { case number(Int) case addition(ArithmeticExpression, ArithmeticExpression) case multiplication(ArithmeticExpression, ArithmeticExpression) }
let five =ArithmeticExpression.number(5) let four =ArithmeticExpression.number(4) let sum =ArithmeticExpression.addition(five, four) let product =ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
let strings = numbers.map { (number) -> Stringin var number = number var output ="" repeat { output = digitNames[number %10]!+ output number /=10 } while number >0 return output } // strings 常量被推断为字符串类型数组,即 [String] // 其值为 ["OneSix", "FiveEight", "FiveOneZero"]
map(_:) 为数组中每一个元素调用了一次闭包表达式。你不需要指定闭包的输入参数 number 的类型,因为可以通过要映射的数组类型进行推断。
在该例中,局部变量 number 的值由闭包中的 number 参数获得,因此可以在闭包函数体内对其进行修改,(闭包或者函数的参数总是常量),闭包表达式指定了返回类型为 String,以表明存储映射值的新数组类型为 String。