2022-02-08
前言
年前换了家公司,很意外其如此巨大的工程在业务代码层已经基本迁移到 Swift,并且实现了完备的业务组件化。不过这也意味着,为适应新工作得开始学习 Swift 语言了。本文开启的这个系列其实大部分是对 Swift 官方文档的翻译,再加了一点点自己的思考和总结。鉴于在 Swift 方面只是菜鸟,所以有任何错漏的地方欢迎各位同行、前辈指正。
关于 Swift 的基础教程,推荐两个来源:
零、Hello World
首先尝试用 Swift 写一个最基本的类型:直接继承自NSObject类型的Person类型。代码如下:
import Foundation
// 注意:可以不声明继承语法,表示声明一个根类,通常情况则会继承 NSObject 根类
// class Person : NSObject {
class Person {
var name: String = "NoName"
var birthday: Date
var age: Int {
get{
//区分变量和常量
let cur = Date()
//注意:这里是Swift的基本类型强制转换语法
return Int(self.birthday.timeIntervalSince(cur))
}
}
init(name: String, birthday: Date) {
// 注意:Swift 根类不包含默认构造函数,所以根类不能调用`super.init()`
// super.init()
self.name = name
self.birthday = birthday
}
convenience init(name: String) {
self.init(name: name, birthday: Date())
}
//注意:需要暴露给 Objective-C 的 Swift 接口,必须要用 @objc 标记
@objc public func speak(_ words: String) {
//更便捷的字符串拼接语法
print(name + " " + String(age) + ": " + words)
}
}
编写以上简短的几句代码其实就可以摸索到 Swift 语言的很多知识点了:
- Swift 不需要用
;分隔语句; - Swift 区分变量类型
var和常量类型let类型; - Swift 支持可选类型,用
?、!声明。在编写代码时,合理地声明可选类型变量,编译器可以在编写代码阶段检测出潜在错误,避免运行时才能发现错误; - Swift 拼接字符串的语法十分便捷;
一、基本类型
Int是Swift 其中一种基本类型,除此之外还包括UInt、Float、Double、Character、String、Date等等。用快捷键跳转到Int的定义,发现Int类型本质是一个结构体,从中可以发现很多“学问”:
- Swift 结构体也有“继承”,但是只能“继承”协议,因此严格意义上说应该是 Swift 的
struct可以实现协议; - Swift 结构体也有构造函数,而且语法更趋近于 Swift 本身的
class构造函数声明语法,而不是 C 语言风格(见附录); - Swift 支持函数编译时多态,即函数重载,这一点有别于 Objective-C,而且构造函数的各种“奇奇怪怪”的形式,还包含了
?、_等特殊符号,前者表示可失败构造函数,后者为; - Swift 支持运算符重载;
// 注意:Int 类型是 struct 结构体。Swift 结构图可以“继承”协议
public struct Int : FixedWidthInteger, SignedInteger {
public typealias IntegerLiteralType = Int
public init(bitPattern x: UInt)
//init(_ source: )函数重载:函数签名1
public init(_ source: Float)
public init?(exactly source: Float)
//init(_ source: )函数重载:函数签名2
public init(_ source: Double)
public init?(exactly source: Double)
//init(_ source: )函数重载:函数签名3
public init(_ source: Float80)
public init?(exactly source: Float80)
//运算符重载
public static func == (lhs: Int, rhs: Int) -> Bool
...
}
至此可以初步认识到,Swift 的结构体类型非常强大,几乎可以说具有了面向对象的特性,又保留了 C 语言结构体值类型的本质。结构体在 Swift 应用开发中的地位,想必会比在 Objective-C 中有质的提升。
附录一:
C 语言结构体的构造函数语法如下。
struct SomeStruct {
int a;
SomeStruct(int _a){
a = _a;
}
};
二、类和对象
Swift 的类的声明语法比 Objective-C 更为简洁,从上面声明Person类的代码可以发现:
- Swift 可以不声明继承语法,表示声明一个根类,通常情况则会继承
NSObject根类,注意根类没有父类,因此调用super.init()是铁定会抛编译错误的; - Swift 类内部的方法使用成员变量时,可以无需使用
self.xxx的形式引用,而直接使用xxx表示成员,方法也是如此; - Swift 类的构造器支持 convenience 构造器,其实现是通过调用当前类的构造器实现;
- Swift 类的属性支持指定默认值;
- Swift 类的属性有两种:存储属性、计算属性,前者有保存真实数据,后者不保存数据;
- Swift 类的构造器中调用
super.init父类的构造器时,必须已初始化所有的存储类型属性,所以 Swift 的构造器中要么先初始化属性值,再调用super.init。要么用以下三种方式之一:1、属性声明为可选属性;2、给属性指定默认值;3、属性声明为懒加载属性。否则会提示Property 'self.name' not initialized at super.init call编译错误;
至此应该只算是发现了 Swift 和 Objective-C 很不同,语法上更加精炼了,结构体特性被史诗级加强,协议应用范围更加广泛,基本类型封装成结构体扩展了大量接口。
附录二:
Swift 中导入 Objective-C 是通过在${PROJECT_NAME}-Bridging-Header.h头文件中#import导入必要的 Objective-C 头文件实现。Objective-C 中导入 Swift 是通过在需要使用 Swift 模块的地方,通过#import ${PROJECT_NAME}-Swift.h导入 Xcode 自动生成的${PROJECT_NAME}-Swift.h头文件,如果发现 Swift 类型的方法对 Objective-C 仍不可见,可以给接口添加@objc标记,以暴露接口。关于 Swift 和 Objective-C 桥接,建议直接参考官方文档。
三、A Swift Tour
这里总结一下 Swift 官方文档“A Swift Tour”章节的要点。这里主要列出与 Objective-C 不太一样的一些“骚操作”。
3.1 基本类型
// 1. Swift 支持类型推断
var myVariable = 42
// 2. Swift 用 var 和 let 关键字区分变量和常量
let myConstant = 42
// 3. Swift 的一般声明语法
let explicitDouble: Double = 70
// 4. Swift 类型强转语法
let widthLabel = String(width)
// 5. Swift 字符串格式化语法
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
// 6. Swift 打印多行文本(想打印单行的话用`+`连接即可)
let quotation = """
I said "I have \(apples) apples."
And then I said "I have \(apples + oranges) pieces of fruit."
"""
// 7. 数组、字典声明语法
var shoppingList = ["catfish", "water", "tulips"]
shoppingList[1] = "bottle of water"
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
// 8. 声明空数组、字典
let emptyArray = [String]()
let emptyDictionary = [String: Float]()
3.2 控制逻辑
// 1. 基本逻辑控制语法,if、for语句均无需圆括号包围
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
// 2. 使用if-let语法进行可空类型解包
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
// 3. 使用??为可空类型提供默认值
let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)"
// 4. 牛逼的switch语句,终于去掉了break这个鸡肋,而且支持字符串类型的判断,支持使用let-where语句
// 实现的模式匹配
let vegetable = "red pepper"
switch vegetable {
case "celery":
print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
print("Is it a spicy \(x)?")
default:
print("Everything tastes good in soup.")
}
// 5. 字典的for-in遍历,得到的key-value元组不再只是key,牛逼
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)
// 6. while语句
while n < 100 {
...
}
// 7. repeat-while语句,相当于do-while
repeat {
...
} while m < 100
// 8. 支持了Python式for-in语句,牛逼
// 其中:`..<`表示不包含最后一个数,`...`表示包含
var total = 0
for i in 0..<4 {
total += i
}
3.3 函数和闭包
// 1. Swift函数的参数有两个名称:内部参数名(parameter name)、外部参数名(argument label)
// 默认使用同一个名称,调用函数时需要显式指定参数名
func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday") //显式指定person参数名
// 2. 可以使用`_`符号表示函数不使用外部参数,则调用函数时不需要指定参数名
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday") //调用时无需指定person参数名
// 3. Swift 函数可以返回元组
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum) // Prints "120"
print(statistics.2) // Prints "120"
// 4. 可以内嵌函数
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()
// 5. 可以返回函数,牛逼,函数地位直线蹿升
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
// 6. 可以把函数作为函数的参数,牛逼,函数式编程雏形初现
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
// 7. Swift 的闭包
numbers.map({ (number: Int) -> Int in
let result = 3 * number
return result
})
// 8. 闭包的简化形式。牛逼,看到了 Python 漂亮的声明式语法风格
let mappedNumbers = numbers.map({ number in 3 * number })
// 9. 闭包的最简化形式尾随闭包,牛逼,可以用脚本风格的`$0`、`$1`代表闭包传入的参数
let sortedNumbers = numbers.sorted { $0 > $1 }
3.4 对象和类
尤其注意 Swift 的构造器的处理逻辑顺序与 Objective-C 有很大区别,如下:
- 设置子类的属性值;
- 调用超类的构造器;
- 设置超类的属性值;
// 1. Swift 类的声明及构造器以及属性
class EquilateralTriangle: NamedShape {
var sideLength: Double = 0.0
// 构造器
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 3
}
// 计算属性的 getter、setter
var perimeter: Double {
get {
return 3.0 * sideLength
}
set {
sideLength = newValue / 3.0
}
}
// 覆盖父类的函数需要使用 override 关键字声明
override func simpleDescription() -> String {
return "An equilateral triangle with sides of length \(sideLength)."
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter) // Prints "9.3"
triangle.perimeter = 9.9
print(triangle.sideLength) // Prints "3.3000000000000003"
// 2. Swift 类的属性的观察期 willSet,除此之外还有 didSet
class TriangleAndSquare {
var triangle: EquilateralTriangle {
willSet {
square.sideLength = newValue.sideLength
}
}
var square: Square {
willSet {
triangle.sideLength = newValue.sideLength
}
}
init(size: Double, name: String) {
square = Square(sideLength: size, name: name)
triangle = EquilateralTriangle(sideLength: size, name: name)
}
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength) // Prints "10.0"
print(triangleAndSquare.triangle.sideLength) // Prints "10.0"
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
3.5 枚举和结构体
// 1. Swift 的枚举一般声明,支持添加函数,继承基本类型的枚举具有rawValue属性
enum Rank: Int {
case ace = 1
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king
func simpleDescription() -> String {
switch self {
case .ace:
return "ace"
case .jack:
return "jack"
case .queen:
return "queen"
case .king:
return "king"
default:
return String(self.rawValue)
}
}
}
let ace = Rank.ace
let aceRawValue = ace.rawValue
// 2. Swift 枚举具有缺省的,用rawValue构建枚举值的` init?(rawValue:)`构造器
// 注意:枚举的缺省构造器是“可空构造器”,有可能返回 nil
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
// 3. 如果枚举没有必要对应rawValue,其声明语法如下,不指定 enum 的类型,此时诸如
// `Suit.heart.rawValue`这样的代码会提示编译错误
enum Suit {
case spades, hearts, diamonds, clubs
func simpleDescription() -> String {
switch self {
case .spades:
return "spades"
case .hearts:
return "hearts"
case .diamonds:
return "diamonds"
case .clubs:
return "clubs"
}
}
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
// 4. Swift 的关联值枚举类型,牛逼,关联值枚举的具体值是在“创建实例时决定的”,也就
// 是说相同枚举成员的不同实例的关联值可以是不相同的。此时“枚举成员”应该被理解为一系
// 列枚举实例的抽象。例如,result 是成功响应数据的抽象,具有两块数据域;failure 是
// 失败响应数据的抽象,只有一块数据域。
enum ServerResponse {
case result(String, String)
case failure(String)
}
let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")
switch success {
case let .result(sunrise, sunset):
print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
print("Failure... \(message)")
}
// 5. Swift 的结构体(实际上 Swift 结构体比 Objetive-C 牛逼很多,这里倒是没说)
// 注意:结构体的缺省构造器是“不可空构造器”,不会返回 nil,若用if-let对构造器返回值
// 进行解包会抛编译错误,提示返回的 Card 并不是可选类型
struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
}
// 由于在 Card 中已经声明了 rank、suit 属性的类型,因此传参用了简化形式
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
3.6 协议和扩展
// 1. Swift 的协议声明,类实现协议
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
class SimpleClass: ExampleProtocol {
var simpleDescription: String = "A very simple class."
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
// 2. 震惊!结构体也能实现协议
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = "A simple structure"
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
// 3. Swift 的扩展相当牛逼,可以对类/结构体扩展,添加方法、协议、属性等等
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription) // Prints "The number 7"
// 4. 可以用协议名声明变量类型
let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
3.7 错误处理
// 1. Swift 抛出异常语法
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
// 2. Swift 捕捉异常基本语法
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
// 3. Swift 捕捉多种异常语法
do {
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
// 4. 可以用`try?`将结果转化为可空类型
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
// 5. 使用 defer 闭包包围函数执行结束后的清理逻辑,初始化逻辑和清理逻辑可以成对聚集
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true
defer {
fridgeIsOpen = false
}
let result = fridgeContent.contains(food)
return result
}
fridgeContains("banana")
print(fridgeIsOpen)
3.8 泛型
// 1. Swift 基本泛型语法,函数
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
var result = [Item]()
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
makeArray(repeating: "knock", numberOfTimes: 4)
// 2. Swift 基本泛型语法,类型
enum OptionalValue<Wrapped> {
case none
case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
// 3. Swift 约束泛型有两种方式:1、使用`:`继承符号;2、使用 where 关键字;
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
where T.Element: Equatable, T.Element == U.Element
{
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
anyCommonElements([1, 2, 3], [3])
四、The Basics
这里总结一下 Swift 官方文档“The Basics”章节的要点。
Swift 封装了几种基本数据类型:Int:整型数;Float、Double:浮点数;String:字符串;集合类型:Set、Array、Dictionary。这些类型都是用Swift 结构体实现。
Swift 对变量的可读写性做了严格的控制,以实现更安全且干净的代码,不可变的变量声明为let,也就是常量,Swift 中的常量比 C 更加强大。
Swift 的元组是一组关联的值,是 C 或 Objective-C 都不具备的特性。Swift 的函数,支持按元组返回多个值。
Swift 支持可空类型(optional type),表示可能有值也可能为空。可空类型使 Swift 更加类型安全,使开发者对代码逻辑有更精准的控制,Swift 也能在编译期发现更多的潜在错误。
Swift 是类型安全语言,开发者在开发期间就能知道所操作的值的类型,例如,声明了一个String变量后,就不能直接将Int类型数据赋值给该String变量,类型安全可以让开发者在开发期间就能及早发现错误。Swift 类型安全检查还包括了可空检查,因此安全性更高。
4.1 常量和变量
// 1. Swift 严格区分常量和变量。使得 Swift 比 Objective-C 具有更高读写安全等级
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0
// 2. 显式指定变量的类型
var welcomeMessage: String
// 3. 支持类型推断,例如下面的代码,编译器能推断 x、y、z 类型为 Double,就无需显示声明类型
var x = 0.0, y = 0.0, z = 0.0
// 4. 声明一连串变量
var red, green, blue: Double
注意:如果想使用 Swift 所保留的关键字作为变量名,例如
var,则可以使用“`”符号包围该变量名。不过,除非万不得已,否则最好还是不要这样用。
var `var` = "Reserved Keyword Named Variable"
4.2 注释
跟 Objective-C 一样。
4.3 分号
Swift 不强制开发者使用分号分割语句。也就是用不用都行。不过既然能不用,那当然不用咯。
let cat = "🐱"; print(cat)
4.4 整型数、浮点数
// 1. 整型数
let minValue = UInt8.min // minValue is equal to 0, and is of type UInt8
let maxValue = UInt8.max // maxValue is equal to 255, and is of type UInt8
// 2. 浮点数
let pi = 3.14159 //默认浮点数类型是 Double
4.5 类型安全和类型推断
前面已经多次提及不再赘述。类型推断是 Swift 强大的特性之一,相比 C 语言和 Objective-C 语言缩减了大量冗余的类型声明的代码。总而言之,Swift 的语法风格就是极简,在类型安全上不仅没有妥协,甚至还更加强悍。
4.6 数字字面量和数字转换
需要尤其注意,Swift 中数值溢出会抛编译错误,这一点和 Objective-C 和 C 不同。
// 1. 各种进制整型数
let decimalInteger = 17
let binaryInteger = 0b10001 // 17 in binary notation
let octalInteger = 0o21 // 17 in octal notation
let hexadecimalInteger = 0x11 // 17 in hexadecimal notation
// 2. 浮点数
let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0
// 3. 添加额外的0位和“_”符号分隔大数
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
// 4. 数字赋值溢出的示例
let cannotBeNegative: UInt8 = -1 //抛错误,无符号数不能赋负值
let tooBig: Int8 = Int8.max + 1 //抛错误,超出 Int8 类型的边界
// 5. 数字类型强转的必要性1
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine //必须强转,否则抛错误
// let pi = 3 + 0.14159 //但是这样是可以编译通过的,因为字面量并没有固定的类型
// 6. 数字类型强转的必要性2
let integerPi = Int(pi)
4.7 类型别名
// 1. Swift 支持类型别名,功能相当于 C 语言的 typedef 关键字
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min
4.8 布尔类型
// 1. 以下语句编译不会通过,原因是 if 语句只能接收 Bool 类型,再次说明 Swift 对类型安全的重视
let i = 1
if i { ... }
// 2. 以下语句可以编译通过
if i == { ... }
4.9 元组
看下例程感受下元组的牛逼。各种灵活骚操作。
// 1. 声明元组
let http404Error = (404, "Not Found")
// 2. 基本元组赋值方式
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)") // Prints "The status code is 404"
print("The status message is \(statusMessage)") // Prints "The status message is Not Found"
// 3. 用“_”忽略元组某成员的,高阶元组赋值方式
let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)") // Prints "The status code is 404"
// 3. 用索引,获取元组成员值的,高阶元组赋值方式,牛逼
print("The status code is \(http404Error.0)") // Prints "The status code is 404"
print("The status message is \(http404Error.1)") // Prints "The status message is Not Found"
// 4. 用成员名称,获取元组成员值的,高阶元组赋值方式,牛逼
let http200Status = (statusCode: 200, description: "OK")
print("The status code is \(http200Status.statusCode)") // Prints "The status code is 200"
print("The status message is \(http200Status.description)") // Prints "The status message is OK"
4.10 可空类型
重点!可空类型的概念在 Objective-C 和 C 语言中并不存在。可空类型表示值可能是有值,也可能没有值。Objective-C 中属性、方法参数、方法返回值的nullable、nonnullable的概念有点接近可空类型的概念,但是只能针对NSObject对象类型,因此在适用范围上不可与 Swift 的可空类型同日而语。
举个例子,使用Int(str)将字符串强制转换为整型数时,并不是所有字符串都能转化为整型数,因此Int()强制转换函数的返回值实际上是一个可空类型,也就是说其返回值类型严格上说是Int?,可能是整型数,也可能是空(什么也不是)。Swift 语言中,用nil表示空。
Swift 类型缺省都是不可空类型,也就是说var a: Int = nil这样的语句是铁定抛编译错误的,编译器可以轻而易取地检测到,给不可空类型赋空值的类型不安全操作。合法的语句是var a: Int? = nil。Swift 的nil的适用范围远大于 Objective-C 的nil,它不仅可以表示指针指向空对象,也可以表示基本类型为空。只能说,强大的一批。
从可空类型取值的操作叫做解包(unwrapping)。Swift 的可空类型解包方法有三种:
- 方法一:强制解包(forced unwrapping)。当可以断定某个可空类型变量在当前上下文肯定不为空,或者通过
if someVar != nil判定目标变量当前不为空,则可以使用someVar!强制解包; - 方法二:使用可空类型绑定(optional binding),也就是前面介绍过的
if-let语法。注意可空类型绑定创建的常量/变量仅在if块中有效,这很好理解,因为else块中它铁定是nil所以也无需用到(注意guard语句不遵循这个规则,后面会看到); - 方法三:隐式解包可空类型(Implicitly Unwrapped optional)。在开发过程中,有时开发者可以从程序结构断定,某个可选类型变量是肯定有值的,此时有必要去除对该变量的解包操作代码。此时可以将变量声明为隐式解包可空类型(implicitly unwrapped optionals),例如,声明为
String!类型,而不是常规的可空类型String?。使用隐式解包可空类型变量时,和使用非可空类型一样。当然,非要带个感叹号也不会有问题。
如果解包隐式解包可空类型变量时,变量实际上是空,程序还是会抛运行时异常。这并不是 Swift 的设计缺陷,而是源于开发者对变量可空性的错误判断。因此,Swift 的可空类型机制,要求开发者对变量是否为空有一个准确的判断。
将隐式解包可空类型赋值给未声明类型的变量时,编译器为后者类型推断的类型是普通的可空类型,并不是隐式解包可空类型。这也比较好理解,因为编译器只能断定在赋值这个点,变量不是空,然而在其他地方就不能保证了。
// 方法一:强制解包
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).")
}
// 方法二:可空类型绑定
if let actualNumber = Int(possibleNumber) {
print("The string \"\(possibleNumber)\" has an integer value of \(actualNumber)")
} else {
print("The string \"\(possibleNumber)\" could not be converted to an integer")
}
// 方法三:隐式解包可空类型
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // no need for an exclamation point
// 注意:下面的语句 Swift 类型推断 optionalString 类型是 String? 而不是 String!
let optionalString = assumedString
到这里开始要蒙圈,问号感叹号一把抓,思绪有点混乱,得捋一捋。其实问号感叹号的用法,无疑就是以下四种组合(并不是所有都合法),一个个撸撸代码试一下就能闹明白了。
${类型}?:声明可空类型;${类型}!:声明隐式解包可空类型;${变量名}?:不合法;${变量名}!:使用可空类型强制解包;
然后再思考一下,前面介绍过的可空构造函数init?又是什么鬼,是不是?还有什么骚操作?其实真没有了,Swift 之所以用init?表示可空构造函数,估计是由于 Swift 构造函数有不声明返回值类型的特殊性,而可空构造函数需要返回可空类型,所以就“蛮”加在函数名后面咯。其他返回可空类型的普通函数用->${类型}?的形式返回就好了,这样一想,init?不就没什么特别的了。
4.11 错误处理
Swift 的错误处理机制(error handling)用于确定程序运行时错误的原因所在,并在必要时向外抛出运行时错误。当函数发生运行时错误时,会抛出错误,此时函数的调用者可以捕捉并处理该运行时错误。Swift 捕捉异常的语法块是do-try-cache,和 Objective-C 不太一样,例程如下:
// 定义可能抛出错误的函数
func makeASandwich() throws {
// ...
}
// 函数调用者使用do-try-catch语法捕捉异常
do {
try makeASandwich()
eatASandwich()
} catch SandwichError.outOfCleanDishes {
washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
buyGroceries(ingredients)
}
4.12 断言和先决条件
断言(assertion)和先决条件(precondition)用于确保程序在运行到某行代码时满足必要的条件,并用极端的程序崩溃的后果来快速定位问题。两者区别在于,前者用于开发阶段,后者用于发布阶段。需要注意,断言和先决条件导致的崩溃是不可捕捉的,因为断言和先决条件失败意味着程序出于一个不可修复的错误状态,必须终止运行。
断言和先决条件的主要意义并不在于避免程序错误状态的出现,而是在于当程序错误状态的出现时,以更加可预测的方式终止程序,来达到快速定位错误的目的。断言和先决条件也有助于限制程序错误状态引发的对程序的破坏,也就是将错误扼杀于摇篮。
断言的两种语法:
// 语法一:断言逻辑表达式
let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
// 语法二:直接断言失败
if age > 10 {
print("You can ride the roller-coaster or the ferris wheel.")
} else if age >= 0 {
print("You can ride the ferris wheel.")
} else {
assertionFailure("A person's age can't be less than zero.")
}
先决条件的两种语法:
// 语法一:使用逻辑表达式
precondition(index > 0, "Index must be greater than zero.")
// 语法二:直接断言失败
if index > 0 {
// 正确的处理代码
} else {
preconditionFailure("Index must be greater than zero.")
}
注意:如果编译选项设置了
-Ounchecked,则程序不会检查先决条件,此时编译器会假定所有先决条件都是true。而fatalError(_:file:line:)函数可以铁定抛出错误。在开发早期编写程序原型时,可以在未实现的函数中调用fatalError(_:file:line:)函数,例如fatalError("Unimplemented"),来提示该函数尚未实现,后续必须实现,否则调用该函数就会崩溃。这是 TDD、BDD 开发过程需要用到的必要技巧,用崩溃来引发单元测试失败。
五、总结
- Swift 是一种类型安全的编程语言;
- Swift 严格区分常量和变量;
- Swift 支持类型推断;
- Swift 字符串支持运算符方式的拼接语法;
- Swift 的基本类型封装为结构体类型,结构体实现了大量便利的工具方法;
- Swift 的结构体支持自定义构造器、实现协议、支持扩展;
- Swift 的可选类型(optional)是其很重要的特征;
- Swift 的函数支持按元组返回;
- Swift 支持泛型编程;
- Swift 支持运算符重载;
- Swift 的函数是一等公民,函数既可以作为函数参数,也可以作为函数返回值,支持内嵌函数;
- Swift 的
switch语句支持字符串类型的判断; - Swift 支持关联值枚举类型,用枚举值关联一类具有特定结构的值;
- Swift 没有统一的根类,可以不继承任何类型创建根类;
- Swift 类的属性分为存储属性和计算属性;
- Swift 类的属性支持默认值;
- Swift 类的属性支持懒加载属性;
- Swift 类的属性支持观察器;
- Swift 类支持 convenience 构造器,通过调用当前类的构造器实现;
- Swift 类的构造器在调用超类的构造器之前,必须先初始化当前类的所有存储属性;
- Swift 的协议十分强大,既可以被类实现,也可以被结构体实现;