Swift5.6 官方文档part1

语言: CN / TW / HK

highlight: a11y-dark

About Swift — The Swift Programming Language (Swift 5.6)

欢迎使用swift

关于swift

swift是一门极好的编写软件的语言, 无论是用来写手机软件、桌面软件、服务器或者是其他跑代码的东西。它是安全、快速、交互的编程语言, 在现代编程语言中结合了最好的、来自于广泛的苹果工程文化和其开源社区的各种贡献。编译器为性能进行了优化, 语言为开发做了优化, 不将就。

swift对于新手来说是友好的。它是一门具备工业品质的编程语言, 同时也是富有表现力和令人愉快的脚本语言。在playground编写swfit让你体验编程并能够马上看到结果, 不需要build和run的开销

swift通过采用现代编程模式, 避免了常规编程的常见错误 * 变量永远在使用之前初始化 * 数组的索引会做越界检查 * Integer会做溢出检查 * 可选项确保nil会被显示处理 * 自动内存管理 * 错误处理允许从异常中进行可控的恢复

swift代码为了能充分利用现代硬件,是编译和优化过的。语法和标准库被设计建立在引导原则上, 按照提示的方式去书写你的代码得到最佳运行效果。它结合了安全和速度, 是从hello world到完整的可操作系统的构建的不错的选择

swift结合了强大的类型接口和现代的、轻量级的语法、允许复杂的想法以简洁明了的方式实现的模式. 代码不单单更容易书写而且更容易阅读和掌握

swift已经发展多年, 也在持续地进化出新功能和能力。我们对于swift充满信心。已经等不及看你使用swift进行创作了。

版本兼容性

书中描绘的是swift5.6, XCode13默认的swift版本, 你也可以使用XCode13构建swift4.2或者swift4。当你使用XCode13构建swift4、swift4.2的代码时, 多数的swift5.6的功能是可用的。以下变化只针对使用swift5.6及以后的代码有效

  • 返回不透明类型的函数需要swift5.1 runtime
  • try?对于已经返回可选项的表达式不再需要引入一个可选值
  • large integer初始化的时候会推断为正确的integer类型

并发需要swift5.6或之后的版本, 以及一个提供了应对并发类型的swift标准库。在苹果平台, 设置目标运行环境最低为iOS15、macOS12、tvOS15 或者 watchOS8.0

一个swift5.6的项目可以依赖于一个swift4.2或者swift4的项目, 反之亦然。如果你存在一个大的项目划分成了多个frameworks, 你可以一次将代码从swift4.0迁移到swift5.6

总览

Hello World

swift print("Hello, world!") // Prints "Hello, world!" 这一行代码直接就能执行 不需要为输入输出功能或者字符串处理导入单独的一个库。在全局范围编写的代码作为程序的入口, 所以也不需要main(), 你也不需要在每句代码后面加分号

  • 建议配合playground使用这一章节, 它能够让你在写代码的时候就看见结果

简单的值

let标识常量, var标识变量。常量的值在编译的时候不需要被知道它的类型, 但你必须声明时给它赋值。你可以声明一个常量然后在其他地方使用 swift var myVariable = 42 myVariable = 50 let myConstant = 42 常量和变量必须和你想给它赋的值是同一个类型。你不需要显式地指定类型, 在你给他们赋值的时候编译器会指定他们的类型。

如果初始值不能提供足够的信息或者没有初始值, 在变量名后面加个冒号, 显式指定类型 swift let implicitInteger = 70 let implicitDouble = 70.0 let explicitDouble: Double = 70

值不会隐式地转化为其他类型, 你需要操作不同类型的值的时候需要显示转换 swift let label = "The width is " let width = 94 let widthLabel = label + String(width)

对于字符串来说有更加简便的转化方式, 字符串内使用\加括号包裹值 swift let apples = 3 let oranges = 5 let appleSummary = "I have \(apples) apples." let fruitSummary = "I have \(apples + oranges) pieces of fruit."

使用三个引号"""包裹住string, string可以占多行, 每个引用行的开头缩进被移除, 只要匹配右引号的锁进 swift let quotation = """ I said "I have \(apples) apples." And then I said "I have \(apples + oranges) pieces of fruit." """ 创建字典和数组使用方阔号, 通过[index]或者[key]去访问某一个值 ```swift var shoppingList = ["catfish", "water", "tulips"] shoppingList[1] = "bottle of water"

var occupations = [ "Malcolm": "Captain", "Kaylee": "Mechanic", ] occupations["Jayne"] = "Public Relations" ```

当你向数组里添加元素的时候数组自动增长 swift shoppingList.append("blue paint") print(shoppingList)

使用初始化器语法创建一个空的字典或者数组 swift let emptyArray: [String] = [] let emptyDictionary: [String: Float] = [:] 如果类型信息能够被推断出来, 你可以用以下的方式去写一个空的数组、空的字典, swift shoppingList = [] occupations = [:]

控制流

使用ifswitch去构建条件语句, 使用for-inwhilerepeat-while去构建循环。使用圆括号去包裹条件或者循环变量是可选的, 主体内容的大阔号是必须的 swift let individualScores = [75, 43, 103, 87, 12] var teamScore = 0 for score in individualScores { if score > 50 { teamScore += 3 } else { teamScore += 1 } } print(teamScore) // Prints "11" 在if语句当中, 条件值必须是布尔值表达式, 这意味着if score {...}这样的代码是错误的, 它不会将其和0值进行比较。

你可以使用if和let一起处理可能缺失的值, 这个可能缺失的值是一个可选项, 一个可选项的值可以是一个值, 也可以是nil表示这个值是缺失的。在变量的类型后面加上问号表示这个变量是一个可选项。 ```swift var optionalString: String? = "Hello" print(optionalString == nil) // Prints "false"

var optionalName: String? = "John Appleseed" var greeting = "Hello!" if let name = optionalName { greeting = "Hello, (name)" } ```

如果可选项为nil, 上面的条件值是false, 大括号里面的代码不会执行。如果可选项不为nil, 则会被解包并赋值给let常量, 在大括号内, 这个let常量是有效的。

另外一种处理可选项的方式是使用??提供一个默认值。如果可选项的值是缺失的, 默认的值会代替被使用 swift let nickname: String? = nil let fullName: String = "John Appleseed" let informalGreeting = "Hi \(nickname ?? fullName)"

switch支持任何类型的数据和广泛种类的比较操作。不再限制成integer, 而是检测值是否相等 swift 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.") } // Prints "Is it a spicy red pepper?"

注意上面let常量是如何在模式中被使用的, 用来将一个匹配模式的值赋值给常量。

执行完switch内某一个匹配的switch之后, 程序会从switch语句中退出, 不会继续执行下一个case, 你不再需要显示地去在每一个case的末尾break;

你可以使用for-in去遍历字典里面的每个item, 通过提供一对名字去使用每一个键值对。字典是一个无序的集合, 所以键值对按照一个任意的顺序被遍历。 swift 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 (_, numbers) in interestingNumbers { for number in numbers { if number > largest { largest = number } } } print(largest) // Prints "25"

使用while去执行代码直到条件发生改变, 也可以将while放到后面, 保证循环体至少执行一次 ```swift var n = 2 while n < 100 { n *= 2 } print(n) // Prints "128"

var m = 2 repeat { m *= 2 } while m < 100 print(m) // Prints "128" ```

有index的循环, 配合..<限制范围 swift var total = 0 for i in 0..<4 { total += i } print(total) // Prints "6" ..<表示不包含右边的值, ...表示包含右边的值

函数和闭包

使用func声明一个函数, 使用函数的时候, 方法名 + 括号包裹的参数列表。在申明一个函数的时候, 使用->分割参数列表和返回类型 swift func greet(person: String, day: String) -> String { return "Hello \(person), today is \(day)." } greet(person: "Bob", day: "Tuesday")

默认的函数使用参数名作为参数外部标签, 你可以在参数名前面设置自定义的参数标签, 也可以使用_表示不需要外部标签 swift func greet(_ person: String, on day: String) -> String { return "Hello \(person), today is \(day)." } greet("John", on: "Wednesday")

使用元组去创建一个组合值, 比如, 从一个函数返回多个值。元组的值可以通过名字或者序号获取。 ```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" 函数可以被嵌套, 嵌套的函数可以访问在函数外部的变量, 你可以使用嵌套函数去组织较长的或者复杂函数的代码swift func returnFifteen() -> Int { var y = 10 func add() { y += 5 } add() return y } returnFifteen() ```

函数是first-class类型, 一个函数可以作为一个函数的返回值 swift func makeIncrementer() -> ((Int) -> Int) { func addOne(number: Int) -> Int { return 1 + number } return addOne } var increment = makeIncrementer() increment(7)

一个函数可以是另一个函数的参数 swift 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) 函数是特殊类型的闭包, 闭包是一段可以在之后被调用的代码, 闭包内的代码可以访问到闭包创建环境下的变量和函数, 即便闭包在另外一个完全不同的环境中被执行,比如上边的嵌套函数。你可以通过用花括号包裹去写一个匿名闭包, 使用in去分割参数和返回类型 以及 闭包主体 swift numbers.map({ (number: Int) -> Int in let result = 3 * number return result }) 你有好几种方式去简化闭包。当一个闭包的类型已经被确认, 比如delegate的回调, 你可以省略参数的类型, 或者返回值的类型, 或者都省略。单个语句的闭包会隐式地返回这个仅有的语句的值 swift let mappedNumbers = numbers.map({ number in 3 * number }) print(mappedNumbers) // Prints "[60, 57, 21, 36]"

你可以通过数字代替参数名去访问参数, 这种方式对于小的闭包来说是非常有用的。一个闭包如果是函数的最后一个参数, 可以直接跟在参数列表圆括号后面。如果是唯一的参数, 那么圆括号可以省略 swift let sortedNumbers = numbers.sorted { $0 > $1 } print(sortedNumbers) // Prints "[20, 19, 12, 7]"

对象和类

使用class后面跟着class的名字去创造一个类。类当中属性申明写法和常量/变量申明一致, 除了属性的声明位于类的语境当中。同样地, 方法和函数申明也是同样的写法 swift class Shape { var numberOfSides = 0 func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } } 在类名后面加一对圆括号可以创建一个类的实例, 使用点语法去访问实例的属性和方法。 swift var shape = Shape() shape.numberOfSides = 7 var shapeDescription = shape.simpleDescription()

类创建一个实例的时候会使用初始化方法init进行设置 ```swift class NamedShape { var numberOfSides: Int = 0 var name: String

init(name: String) {
    self.name = name
}

func simpleDescription() -> String {
    return "A shape with \(numberOfSides) sides."
}

} ```

注意上边在init方法里面通过self. 区分了属性和初始化的参数name。每一个属性都需要初始值, 可以在申明属性的时候给到, 也可以在初始化器里面给到。

使用deinit去创建一个去初始化器, 如果你需要在对象deallocated之前做一些清理。

子类需要在自己的class名字后跟上父类的名字, 使用冒号:分隔。没有必要让一个类显式申明为某一标准根类的子类, 你可以按照自己的需要添加或者省略父类声明。

子类的方法如果需要覆盖父类的实现, 需要使用override标识。没有使用override标识意外覆盖了父类的方法会被编译器检测为一个错误, 编译器也会检测到使用了override标识但是父类当中没有对应的方法。 ```swift class Square: NamedShape { var sideLength: Double

init(sideLength: Double, name: String) {
    self.sideLength = sideLength
    super.init(name: name)
    numberOfSides = 4
}

func area() -> Double {
    return sideLength * sideLength
}

override func simpleDescription() -> String {
    return "A square with sides of length \(sideLength)."
}

} let test = Square(sideLength: 5.2, name: "my test square") test.area() test.simpleDescription() ```

除了简单的存储性属性, 属性可以有getter和setter方法 ```swift class EquilateralTriangle: NamedShape { var sideLength: Double = 0.0

init(sideLength: Double, name: String) {
    self.sideLength = sideLength
    super.init(name: name)
    numberOfSides = 3
}

var perimeter: Double {
    get {
        return 3.0 * sideLength
    }
    set {
        sideLength = newValue / 3.0
    }
}

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" ```

在setter当中, newValue作为隐式的传入的参数名, 你可以使用()跟在set后面提供一个显式的名称

在上边等边三角形的初始化当中有3个不同的步骤: * 设置子类声明的属性值 * 调用父类的初始化方法 * 改变父类当中的定义的属性的值, 其他额外的使用getter、setter方法的设置工作都可以在这一步进行

如果你不需要计算属性, 但是想要在设置一个新值之前或者之后运行一段代码, 可以使用`willSet`和`didSet`。被提供的代码将在每次属性发送变化的时候被运行, 除了初始化以外。以下代码保证了正方形和三角形的边长一直保持一致

swift 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) // Prints "50.0"

当使用可选项的时候, 可以在可选项的方法、属性、下标之前写上?。如果?前面值是nil, 任何?后面的都会被忽略, 而且整个表达式的值是nil。不写上?的话, 可选项会被解包, 之后的一切?作用于被解包的值。两种方式来说, 整个表达式都是可选项. swift let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") let sideLength = optionalSquare?.sideLength

枚举和结构体

使用enum创造枚举, 像类和其他命名类型一样, 枚举可以有自己相关的方法 ```swift 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 ```

默认地, swift枚举初始值从0开始依次增大, 但是你可以通过显式地指定值去改变这一点。上面的类型当中使用1作为Ace的初始值, 剩下的初始值依次增大。你也可以使用字符串或者浮点数作为枚举的初始值。使用rawValue属性去访问一个枚举项的初始值。

使用init?(rawValue:)初始化器去用一个初始值创造一个枚举实例。返回对应值的枚举项, 或者没有对应枚举项, 返回nil swift if let convertedRank = Rank(rawValue: 3) { let threeDescription = convertedRank.simpleDescription() }

枚举项的值就是真正的值, 不单单只是另一种表示他们初始值的方式。如果有没有意义的初始值, 也没有必要提供一个枚举项表示它。 ```swift 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() ```

注意上面hearts的枚举项的两种推导方式, 将hearts枚举项给到常量的时候, 使用的是Suit.hearts, 因为常量没有显式指定类型。而在枚举内部的Description当中, 直接使用了.hearts, 因为self已经被认定为是Suit的枚举。在已经知道类型的情况下, 你可以使用缩写。

如果一个枚举设定了初始值, 这些值被确定为声明的一部分, 这就意味着每一种枚举项的实例都是相同的初始值。另外一种方式就是让枚举项和值进行关联, 只有在你创建实例的时候才会确定, 同一枚举项的值可以是不同的。你可以认为关联的值类似于枚举项实例的属性。比如, 从服务器请求日出、日落时间, 服务器要么同时返回这两个时间, 要么返回出错信息 ```swift 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)") } // Prints "Sunrise is at 6:00 am and sunset is at 8:09 pm." ```

使用struct去创建一个结构体, 结构体支持许多和类相同的行为, 包括类和初始化器。结构体和类之间最大的区别就是, 对于赋值来说, 结构体是做值拷贝, 但是类是通过引用计数, 传递指针。 swift struct Card { var rank: Rank var suit: Suit func simpleDescription() -> String { return "The \(rank.simpleDescription()) of \(suit.simpleDescription())" } } let threeOfSpades = Card(rank: .three, suit: .spades) let threeOfSpadesDescription = threeOfSpades.simpleDescription()

协议和扩展

使用protocol去申明一个协议 swift protocol ExampleProtocol { var simpleDescription: String { get } mutating func adjust() }

类、枚举和结构体都支持协议 ```swift class SimpleClass: ExampleProtocol { var simpleDescription: String = "A very simple class." var anotherProperty: Int = 69105 func adjust() { simpleDescription += " Now 100% adjusted." } } var a = SimpleClass() a.adjust() let aDescription = a.simpleDescription

struct SimpleStructure: ExampleProtocol { var simpleDescription: String = "A simple structure" mutating func adjust() { simpleDescription += " (adjusted)" } } var b = SimpleStructure() b.adjust() let bDescription = b.simpleDescription ```

注意到上面结构体当中使用了mutating, 去标识一个将会修改结构体的方法。而类不需要这个标识, 因为类当中的方法总是能够修改类。

使用extension去对已经存在的类型添加功能, 比如新的方法和计算型属性。你可以使用扩展对申明在任何其他位置的类型添加协议的实现, 甚至是你从库或者framework当中导入的类型。 swift extension Int: ExampleProtocol { var simpleDescription: String { return "The number \(self)" } mutating func adjust() { self += 42 } } print(7.simpleDescription) // Prints "The number 7" 像其他命名类型一样, 你可以使用协议名表示一个类型, 比如创建一个类型不同但是遵循同一协议的集合。但是当你操作这些类型为协议类型的对象的时候, 协议以外的方法是不可以使用的 swift let protocolValue: ExampleProtocol = a print(protocolValue.simpleDescription) // Prints "A very simple class. Now 100% adjusted." // print(protocolValue.anotherProperty) // Uncomment to see the error

虽然上面的protocolValue运行时类型是SimpleClass, 但是编译器会将其作为ExampleProtocoll来处理, 这意味着你不能访问除了在协议里面规定的属性和方法

错误处理

你可以使用遵循了Error协议的任何类型来代表errors swift enum PrinterError: Error { case outOfPaper case noToner case onFire } 使用throw去抛出错误, 使用throws去标记一个可能抛出错误的函数。如果你在一个函数内抛出了错误, 函数会立刻返回, 调用这个函数的代码需要处理这个错误 swift func send(job: Int, toPrinter printerName: String) throws -> String { if printerName == "Never Has Toner" { throw PrinterError.noToner } return "Job sent" }

存在几种处理错误的方式, 一种是使用do-catch。在do的代码块当中, 你需要在可能抛出错误的代码前面标识try。在catch代码块当中, 错误会使用error作为错误名 swift do { let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng") print(printerResponse) } catch { print(error) } // Prints "Job sent"

你可以提供多个catch代码块去处理特定的错误, 一个catch紧接上一个catch, 类似于switch 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) } // Prints "Job sent"

另外一种处理错误的方式是使用try?去将结果转化为可选项。如果抛出错误, 错误会被丢弃, 返回值是nil, 否则返回值是一个包含了函数返回值的可选项。 swift let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler") let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")

使用defer去写一段在这个函数所有代码之后执行的代码块, 在函数返回之前。 无论是否抛出异常都会执行。你可以使用defer去写设置和清理的代码, 成对出现但是在不同时间执行。 ```swift 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) // Prints "false" ```

泛型

使用尖括号去构造一个范型函数或者类型 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)

函数、方法、类、枚举和结构体都支持范型 swift // Reimplement the Swift standard library's optional type enum OptionalValue<Wrapped> { case none case some(Wrapped) } var possibleInteger: OptionalValue<Int> = .none possibleInteger = .some(100)

使用where在函数体右边去指定一个需求列表, 比如要求类型服从某个协议、两个参数是同一种类型、有一个特定的父类 swift 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])

<T: Equatable>是等价于<T>... where T: Equatable