学习Swift语言(一)Hello Swift

1,277 阅读8分钟

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 其中一种基本类型,除此之外还包括UIntFloatDoubleCharacterStringDate等等。用快捷键跳转到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:整型数;FloatDouble:浮点数;String:字符串;集合类型:SetArrayDictionary。这些类型都是用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 中属性、方法参数、方法返回值的nullablenonnullable的概念有点接近可空类型的概念,但是只能针对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 的协议十分强大,既可以被类实现,也可以被结构体实现;