{"id":20948455,"url":"https://github.com/ajiew/headfirstswift","last_synced_at":"2025-07-16T09:35:53.297Z","repository":{"id":107895097,"uuid":"569205458","full_name":"aJIEw/HeadFirstSwift","owner":"aJIEw","description":"一个安卓开发者的 Swift 学习笔记。","archived":false,"fork":false,"pushed_at":"2023-10-23T14:51:10.000Z","size":87,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-13T04:44:09.039Z","etag":null,"topics":["headfirst","ios","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aJIEw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-22T10:09:50.000Z","updated_at":"2023-03-04T09:07:03.000Z","dependencies_parsed_at":null,"dependency_job_id":"ca3aad64-5831-4391-9028-192b95fbd566","html_url":"https://github.com/aJIEw/HeadFirstSwift","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/aJIEw/HeadFirstSwift","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aJIEw%2FHeadFirstSwift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aJIEw%2FHeadFirstSwift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aJIEw%2FHeadFirstSwift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aJIEw%2FHeadFirstSwift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aJIEw","download_url":"https://codeload.github.com/aJIEw/HeadFirstSwift/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aJIEw%2FHeadFirstSwift/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265500240,"owners_count":23777441,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["headfirst","ios","swift"],"created_at":"2024-11-19T00:19:04.893Z","updated_at":"2025-07-16T09:35:53.277Z","avatar_url":"https://github.com/aJIEw.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"## HeadFirstSwift\n\nSwift 是在 2014 年苹果开发者大会上面世的新语言，相比 Objective-C，它的语法更简洁，也更现代化。\n\n这篇学习笔记主要参考自 [Swift 官方文档](https://docs.swift.org/swift-book/)。\n\n### The Basics\n\nSwift 是一门现代化的语言，它的语法和 Kotlin 非常相似，因此，我这里只会整理出一些特殊的地方。\n\n#### 常量和变量\n\nSwift 中使用 `let` 关键字声明常量，`var` 关键字声明变量。\n\n```swift\nlet constant: String = \"Swift\"\nvar version = \"5.7.1\"\nprint(\"Hello, \\(constant) \\(version)!\")\n```\n\n不同于 Objective-C，Swift 不需要一个 `main` 方法作为程序入口，我们可以直接将上述代码保存成文件，比如 `basics.swift`，然后使用下面的命令运行它：\n\n```sh\nswift basics.swift\n```\n\n#### 类型别名\n\n给已有的类型添加别名，用关键字 `typealias` 表示。\n\n```swift\n// 定义别名 unsigned UInt 16\ntypealias AudioSample = UInt16\n\n// 使用别名调用原来类型上的属性或方法\nvar maxAmplitudeFound = AudioSample.min\n```\n\n使用别名可以增加代码的可读性，我们可以在某些情况下，根据当前上下文为类添加别名。\n\n#### Tuple\n\n元组是 Swift 中独有的数据类型，用于表示一组值。\n\n```swift\n// 用 () 创建元组\nlet http404Error = (404, \"Not Found\")\n\n// 解构赋值\nlet (statusCode, statusMessage) = http404Error\n\n// 根据下标访问元组\nprint(http404Error.1)\n\n// 也可以给元素命名\nlet http200Success = (code: 200, result: \"OK\")\nprint(\"The result is \\(http200Success.result))\n```\n\n元组最大的用途是在方法中作为返回值，当我们需要返回多个不同类型的数据时就可以使用元组。\n\n#### Optionals\n\n类似于 Kotlin 中的 nullable 和 Dart 中的 null safe，用于表示一个变量可能没有值。\n\n用法也基本一致，不同的地方在于，它需要使用 `!` 转换 (*unwrap*) 后才能使用：\n\n```swift\nvar serverResponseCode: Int? = nil\n\nif serverResponseCode != nil {\n\t  print(\"response code: \\(serverResponseCode!)\")\n}\n```\n\n##### Optional Binding\n\n我们可以使用 optional binding 的语法检查某个 optional 是否有值，语法如下：\n\n```swift\nif let constantName = someOptional {\n    // now you can use `constantName`\n}\n```\n\n需要注意的是，通过这种语法创建的常量，只有在该语句下才能使用。\n\n##### Implicitly Unwrapped Optionals\n\n当我们确定某个可选变量一定有值时，可以使用隐式转换，使得一个可选变量可以直接访问而不用再进行转换。\n\n```swift\nlet assumedString: String! = \"An implicitly unwrapped optional string.\"\nlet implicitString: String = assumedString\n```\n\n#### Assertions and Preconditions\n\n我们可以使用 `assert` 和 `precondition` 检测条件是否满足，它们两者的区别是 `assert` 只在 debug 阶段运行，而 `precondition` 则在 debug 和 production 下都会运行。\n\n```swift\nvar index = 3\nprecondition(index \u003e 3)\nassert(index \u003e 3)\n```\n\n### String\n\nSwift 中的字符串用法和 Kotlin 基本一致，比如多行字符串等。\n\n#### 扩展分隔符\n\n当字符串中包含多个转义字符的时候，我们可以使用扩展分隔符，语法是在字符串前后都加上 `#`：\n\n```swift\nprint(#\"Write an \"extended delimiters\" string in Swift using #\\n\".\"#)\n```\n\n#### 字符串插值\n\nKotlin 中可以使用 `$` 在字符串中引用变量，Swift 中使用 `\\()`。\n\n```swift\nlet someString = \"some string literals \\(3*2)\\n\"\nprint(\"some string = \\(someString)\")\n```\n\n#### 字符串常用方法\n\n##### 长度和字符串下标\n\n通过 `count` 获取字符串长度。由于 Swift 中字符串中的每个字符可能使用不同数量的内存空间，所以无法直接使用 int 下标访问，而必须通过字符串下标 ([*String.Index*](https://developer.apple.com/documentation/swift/string/index))：\n\n```swift\nvar str = \"Swift\"\nprint(\"count: \\(str.count)\")\n\nlet endIndex = str.endIndex\nlet startIndex = str.startIndex\n\nvar lastCharIndex = str.index(before: endIndex)\nprint(\"first: \\(str[startIndex]), last: \\(str[lastCharIndex])\")\n\nvar charIndex = str.index(startIndex, offsetBy: 2)\nprint(\"char at \\(charIndex.utf16Offset(in: str)): \\(str[charIndex])\")\n```\n\n上面的例子中，`startIndex` 表示字符串中第一个字符的下标，`endIndex` 表示最后一个字符之后的下标。因此，当字符串为空时，这两个值相同。\n\n我们使用 `index` 方法获取最后一个字符的下标，然后通过 `[indexValue]` 语法获取字符串中的字符。\n\n##### 添加和删除\n\n```swift\nstr.insert(\"!\", at: endIndex)\nprint(\"insert !: \\(str)\")\n\nstr.insert(contentsOf: \"Hello \", at: startIndex)\nprint(\"insert Hello: \\(str)\")\n\nstr.remove(at: str.index(before: str.endIndex))\nprint(\"remove last char: \\(str)\")\n\nlet range = startIndex..\u003cstr.firstIndex(of: \"S\")!\nstr.removeSubrange(range)\nprint(\"remove first word: \\(str)\")\n```\n\n#### Substring\n\n[Substring](https://developer.apple.com/documentation/swift/substring) 是字符串的一个切片，它是一个独立的类型，但是使用方式和字符串基本一致，而且所有的操作都很高效，因为 Substring 共享原有字符串的存储空间。\n\n```swift\nlet greeting = \"Hi, Swift!\"\n\nlet start = greeting.firstIndex(of: \"S\")!\nlet end = greeting.firstIndex(of: \"!\")!\nlet substring = greeting[start...end]\nprint(\"substring =\\\"\\(substring)\\\"\")\n\nvar substringToNewString = String(substring))\n```\n\n### Collections\n\nSwift 中有三种基本的集合类型，[Array](https://developer.apple.com/documentation/swift/array) / [Set](https://developer.apple.com/documentation/swift/set) / [Dictionary](https://developer.apple.com/documentation/swift/dictionary)。\n\n#### Arrays\n\n```swift\nvar someInts: [Int] = [0, 1]  // 类型可省略\nvar threeInts = Array(repeating: 2, count: 3)\nvar twoInts = [Int](repeating: 3, count: 2)\nvar combinedInts = someInts + threeInts + twoInts\nprint(combinedInts)\n\nvar basket = [\"apple\", \"banana\", \"orange\"]\nbasket.insert(\"blueberry\", at: 1)\nprint(basket)\nbasket.removeLast();\nprint(basket)\n\nfor (index, value) in basket.enumerated() {\n    print(\"Item at \\(index): \\(value)\")\n}\n```\n\n#### Set\n\n```swift\nvar langs: Set\u003cString\u003e = [\"kotlin\", \"dart\", \"swift\"]\nif langs.contains(\"swift\") {\n    print(\"Hi, Swift!\")\n}\n```\n\n#### Dictionary\n\n```swift\nvar namesOfIntegers: [Int: String] = [:]\nnamesOfIntegers[1] = \"one\" // 使用 subscript 访问或赋值\nprint(namesOfIntegers)\n\nvar languageBirth = [\"Kotlin\": 2011, \"Dart\": 2011, \"Swift\": 2014]\nvar keys = [String] (languageBirth.keys) // to array\nprint(\"languages: \\(keys)\")\n\nfor (lang, year) in languageBirth {\n    print(\"\\(lang) is first published at \\(year)\")\n}\n\nlanguageBirth[\"Dart\"] = nil // remove item\nprint(languageBirth)\n```\n\n### Control Flow\n\n#### For-In Loops\n\n```swift\n// closed range operator\nfor index in 1...3 {\n    print(\"\\(index) times 5 is \\(index * 5)\")\n}\n\n// half-open range operator\nfor tick in 1..\u003c3 {\n    print(\"count to \\(tick)\")\n}\n\n// stride(from:through/to:by:) function\nfor tick in stride(from: 3, through: 12, by: 3) {\n    print(\"upgrade to \\(tick)\")\n}\n```\n\n#### While Loops\n\n```swift\nvar gameRound = 1\nwhile gameRound \u003c= 3 {\n    print(\"Playing round \\(gameRound)\")\n    gameRound += 1\n}\nprint(\"Game over\")\n\nlet daysInWeek = 5\nvar workingDays = 0\nrepeat {\n    print(\"Day \\(workingDays + 1): Today you're going to work\")\n    workingDays += 1\n} while workingDays \u003c daysInWeek\n```\n\n#### Switch\n\nSwift 中的 `switch` 语法和其它语言基本一样，不同的是在 `case` 判断中支持更多的模式匹配，比如支持多个值判断、区间判断、元组、临时赋值等。具体请看 [`control_flow.swift`](./src/control_flow.swift)。\n\n#### Optional Chaining\n\n我们可以链式调用可为空的值：\n\n```swift\nif let hasRmb = person.wallert?.rmb?.notEmpty {\n    print(\"You can pay with RMB.\")\n} else {\n    print(\"Insufficient funds.\")\n}\n```\n\n#### guard\n\n`guard` 和 `if` 的区别在于，它要求条件必须为 `true` 才能执行后面的代码，并且总是包含一个 `else` 语句。\n\n```swift\nfunc greet(person: [String: String]) {\n    guard let name = person[\"name\"] else {\n        // else 中必须使用 return 或者 throw 抛出异常中断后续代码的执行\n        return\n    }\n\n  \t// 使用 guard 后，optional binding 中的变量在后面的作用域中也可用了\n    print(\"Hello \\(name)!\") // 将 guard 改成 if 后再试试\n\n    guard let location = person[\"location\"] else {\n        print(\"I hope the weather is nice near you.\")\n        return\n    }\n\n    print(\"I hope the weather is nice in \\(location).\")\n}\n```\n\n#### available\n\n我们可以在执行代码前判断 API 是否可用：\n\n```swift\nfunc chooseBestColor() -\u003e String {\n    guard #available(macOS 10.12, *) else {\n        return \"gray\"\n    }\n    let colors = ColorPreference()\n    return colors.bestColor\n}\n```\n\n### Function\n\nSwift 中的方法同样是 first-class 的，方法可以作为参数，也可以作为返回值。\n\n一个典型的 Swift 方法定义如下：\n\n```swift\n// 方法名称是：greeting(name:)\nfunc greeting(name: String) -\u003e String {\n    return \"Hi, \\(name)!\"\n}\n```\n\nSwift 中的方法参数与 Objective-C 中一样，是方法名称的一部分，参数分为标签 (*argument label*) 和名称 (*parameter name*) 两部分。假如我们没有定义标签，则**默认使用参数名称作为标签**，如果想要使用不具名参数，则可以使用 `_` 作为标签。\n\n```swift\n// 方法名称是：greeting(for:)，参数标签和参数名称不同\nfunc greeting(for name: String) -\u003e String {\n    return \"Hi, \\(name)!\"\n}\n\n// 方法名称是：greeting(_:)，参数无标签\nfunc greeting(_ name: String) -\u003e String {\n    return \"Hi, \\(name)\"\n}\n```\n\n此外，方法的参数还支持默认值、动态参数列表 (*variadic parameters*) 等。和 Kotlin 不同，Swift 中的参数还可以使用 `inout` 关键字来支持修改参数的值，具体请看 [`function.swift`](./src/function.swift)。\n\n#### 方法类型\n\n我们可以将方法作为数据类型使用：\n\n```swift\n// 作为变量\nvar mathFunction: (Int, Int) -\u003e Int = addTwoInts\n\n// 作为参数\nfunc printMathResult(_ mathFunction: (Int, Int) -\u003e Int) {\n    mathFunction(1, 2)\n}\n\n// 作为返回值\nfunc produceMathFunction() -\u003e (Int, Int) -\u003e Int {\n    return addTwoInts\n}\n```\n\n#### Closure\n\nSwift 中的 closure 类似于 Kotlin 中的 lambda 方法以及 Objective-C 中的 blcoks。\n\n```swift\nlet languages = [\"Kotlin\", \"Dart\", \"Swift\"]\nlet sortedWithClosure = languages.sorted(by: {\n  (s1: String, s2: String) -\u003e Bool in s1 \u003c s2\n})\n```\n\n以上例子中，`sorted` 方法的 `by` 参数接收的是一个方法类型，因此我们可以创建一个 closure 去完成调用。一个 closure 的完整语法如下：\n\n```swift\n{ (parameters) -\u003e returnType in\n    statements\n}\n```\n\n当参数和返回值的类型可以被推断出来时，可以简写成以下的形式：\n\n```swift\nlet sortedWithClosureOneLiner = languages.sorted(by: { s1, s2 -\u003e in s1 \u003c s2 })\n```\n\n另外，在 closure 中，参数可以缩写成 `$index` 的形式，因此，以上调用可以进一步缩写成：\n\n```swift\nlet sortedWithClosureNoArgs = languages.sorted(by: { $0 \u003c $1 })\n```\n\n我们甚至可以利用操作符方法 (*Operator Methods*) 继续缩写以上调用，由于 `\u003e` 或 `\u003c` 在字符串中被定义为接受两个字符串参数返回值为布尔类型的方法，正好符合 `sorted` 中 `by` 接收的方法类型，因此，我们可以直接传递操作符：\n\n```swift\nlet sortedWithClosureOperator = languages.sorted(by: \u003c)\n```\n\n##### Trailing Closure\n\n类似于 Kotlin 中的 trailing lambda，当方法最后一个参数是一个 closure 时，我们可以将 closure 调用写在 `()` 之外。而当如果 closure 是调用的唯一的参数时，则可以省略 `()`。\n\n上面的例子用拖尾 closure 表示就是：\n\n```swift\nlet sortedWithTrailingClosure = languages.sorted { $0 \u003c $1 }\n```\n\n不同之处在于，Swift 中的拖尾表达式可以链式调用：\n\n```swift\n// 模拟下载方法\nfunc download(_ file: String, from: String) -\u003e String? {\n    print(\"Downloading \\(file) from \\(from)\")\n    return Int.random(in: 0...1) \u003e 0 ? file : nil\n}\n\n// 下载图片方法，包含两个方法类型的参数\nfunc downloadPicture(name: String, onComplete: (String) -\u003e Void, onFailure: () -\u003e Void) {\n    if let picture = download(name, from: \"picture sever\") {\n        onComplete(picture)\n    } else {\n        onFailure()\n    }\n}\n```\n\n使用链式 closure 调用该方法：\n\n```swift\ndownloadPicture(name: \"photo.jpg\") { picture in\n    print(\"\\\"\\(picture)\\\" is now downloaded!\")\n} onFailure: {\n    print(\"Couldn't download the picture.\")\n}\n```\n\n##### Capturing Values\n\n当我们使用 closure 时，有时会发生捕获 closure 作用域之外的值的现象：\n\n```swift\nfunc makeIncrementer(forIncrement amount: Int) -\u003e () -\u003e Int {\n    var runningTotal = 0\n    // 捕获外部作用域的值\n    func incrementer() -\u003e Int {\n        // incrementer 方法内部会保留 runningTotal 和 amount 的引用，\n        // 直到该方法不再被使用\n        runningTotal += amount\n        return runningTotal\n    }\n    return incrementer\n}\n```\n\n### Enumeration\n\n枚举值语法：\n\n```swift\nenum CompassPoint {\n    case north\n    case south\n    case east\n    case west\n}\n\n// or\nenum CompassPoint {\n    case north, south, east, west\n}\n```\n\n使用枚举类：\n\n```swift\nvar direction = CompassPoint.north\n// 枚举变量定义后修改可以不写枚举类\ndirection = .west\n\nswitch direction {\ncase .north:\n    print(\"Lots of planets have a north\")\ncase .south:\n    print(\"Watch out for penguins\")\ncase .east:\n    print(\"Where the sun rises\")\ncase .west:\n    print(\"Where the skies are blue\")\n}\n\n// 继承 CaseIterable 后就可以遍历枚举类的所有值了\nenum CompassPoint: CaseIterable { ... }\n\n// 使用 allCases 属性获取所有枚举值\nprint(\"There're \\(CompassPoint.allCases.count) directions, and they are:\")\n// 遍历\nfor direction in CompassPoint.allCases {\n    print(direction)\n}\n\n// 枚举类默认是没有原始数据类型 (raw type) 的，但是我们可以通过继承自指定类型后获得\n// 比如继承 Int，则原始数据类型就是随定义位置自增的 Int，继承自 String 就是对应的字符串\nenum Planet: Int {\n    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune\n}\n// 使用 rawValue 访问原始数据\nprint(\"Earth is No.\\(Planet.earth.rawValue) in the solar system.\")\n\n\n// enum 类也可以保存值 (associated value)\nenum ListType {\n    case vertical(Int)\n    case horizontal(Int, String)\n}\n\n// 获取值\nswitch(listType) {\ncase .vertical(let column):\n    print(\"It's a vertical list with \\(column) columns\")\ncase let .horizontal(row, id):\n    print(\"It's a horizontal list with \\(row) rows, id = \\(id)\")\ndefault:\n    print(\"Unknow type\")\n}\n```\n\n### Structures and Classes\n\n#### 模块\n\n到目前为止，以上所有代码执行环境都可以视为在脚本中执行，但是，当我们开始构建大型项目之后，我们不可能将所有的代码都写在一个文件中，我们需要将代码抽象化成结构和类以复用代码。除此之外，我们还可能需要将自己的代码封装成一个模块 (*module*) 给其他人调用。\n\n在 Swift 中，一个模块是一个最小的代码分发单元，它可以表示一个库 (*library*)、框架 (*framework*)、可执行包 (*executable package*)、应用 (*application*) 等，当导入一个模块后，你就可以使用其中任何公共的方法和数据模型了。\n\n虽然 `struct` 和 `class` 可以定义在同一个文件中，但是我觉得最好还是创建一个新的模块，用单独的文件保存，因此，先介绍一下模块的知识，关于访问控制 (*Access Control*) 相关的知识之后再慢慢介绍。\n\n##### Swift Package Manager\n\n我们可以使用 Swift 包管理器创建和发布库 (*library*) 和可执行包 (*executable package*)。\n\n```sh\nmkdir MySwiftPackage\ncd MySwiftPackage\n\n# 在 MySwiftPackage 文件夹下创建一个可执行包\nswift package init --type executable\n# 运行\nswift run\n```\n\n- 基本使用：[Using the Package Manager](https://www.swift.org/getting-started/#using-the-package-manager)\n- 具体用法：[swift-package-manager](https://github.com/apple/swift-package-manager/blob/main/Documentation/Usage.md)\n- API 文档：[Package Description API](https://docs.swift.org/package-manager/PackageDescription/index.html)\n\n#### Struct vs Class\n\n`struct` 和 `class` 在 Swift 都是组织代码的基本结构，它们有很多共同点，比如：\n\n- 都可以定义属性、方法和 subscripts\n- 都可以定义初始化器\n- 都可以被扩展增加功能\n- 都可以实现 protocols 来实现特定用途\n\n不同之处在于，类相比结构有更多特殊功能：\n\n- 类可以被继承\n- 类支持类型转换和运行时类型检查\n- 可以在销毁方法中释放资源\n- 可以使用引用计数，一个类实例可以有多个引用\n\n除此之外，`struct` 和 `class` 的用法基本一致，定义一个 struct：\n\n```swift\nstruct Resolution {\n    var width = 0\n    var height = 0\n}\n```\n\n定义一个类：\n\n```swift\nclass VideoMode {\n    var resolution = Resolution()\n    var interlaced = false\n    var frameRate = 0.0\n    var name: String?\n}\n```\n\n创建实例：\n\n```swift\n// struct 有默认的初始化方法 (memberwise initializer)，可以在其中为所有属性赋值\nvar hd = Resolution(width: 1920, height: 1080)\n\n// 类只有一个默认构造器\nvar videoMode = VideoMode()\n```\n\n#### 作为值类型的 `struct` 和 `enum`\n\n在 Swift 中，变量和常量分为两种类型，一种是作为值类型，一种是作为引用类型，基本数据类型 `Int` `Float` `Double` `Bool` `String`，以及集合类型 `Array` `Set` `Dictionary` 都是值类型，它们背后都是基于 `struct` 实现的。也就是说，当把它们作为变量传递时，传递的都是值的拷贝，而不是引用。\n\n`enum` 实例也是一样：\n\n```swift\nvar direction = CompassPoint.north\nvar newDirection = direction\ndirection = .west\nprint(newDirection) // still north!\n```\n\n因此，当我们需要改变值类型时，一般需要在实例方法上添加 `mutating` 关键字：\n\n```swift\nmutating func changeDirection(_ newDirection: CompassPoint) {\n    self = newDirection\n}\n```\n\n#### 作为引用类型的 `class`\n\n不同于 `struct`，类的实例是作为引用类型使用的，这点毋庸置疑。\n\n```swift\nvar normal = VideoMode()\nnormal.name = \"Normal\"\nnormal.resolution = sd\nnormal.frameRate = 60\nprint(\"\\(normal.name!)-\u003e resolution: \\(normal.resolution)\")\n\nvar blueray = normal\nblueray.name = \"Blueray\"\nnormal.resolution = hd\nnormal.frameRate = 90\nprint(\"\\(blueray.name!)-\u003e resolution: \\(blueray.resolution)\")\n```\n\n以上例子中，`blueray` 拷贝了 `normal` 的引用，所以当我们修改 `normal` 的属性时，`blueray` 的属性也会得到修改。\n\n为了判断两个类的实例是否相等，Swift 中引入了一致性判断操作符 (*Identity Operator*) `===` 和 `!==`：\n\n```swift\nif normal === blueray {\n    print(\"They are the same.\")\n}\n```\n\n#### Properties\n\n根据是否保存值来看，属性可以分成两种，分别是存值属性 (*Stored Properties*) 和计算后属性 (*Computed Properties*)。\n\n##### Stored Properties\n\n存值属性通常在类 (`class`) 和结构 (`struct`) 中作为常量 (`var`) 和变量 (`let`) 使用。\n\n```swift\nstruct SomeStruct {\n    let someConstant = 0\n    var someVar = 0\n    // 延迟初始化属性\n  \tlazy var importer = DataImporter()\n}\n```\n\n##### Computed Properties\n\n计算后属性除了可以在类和结构中使用外，也可以在枚举类中使用，它不保存值，但是会提供 getter 方法和可选的 setter 方法从其它属性或值间接地计算或设值后得到。\n\n```swift\nclass SomeClass {\n    var computed: Int = {\n        get {\n            // 根据其它属性计算得到返回值\n        }\n        // optional\n        set(newValue) {\n            // 更新相关联的属性，从而计算新值\n        }\n        // 也可以不写 setter，直接使用返回值作为 getter\n        // 那么，该计算后属性就是 read-only 的\n        // return 10\n    }\n}\n```\n\n##### Property Observers\n\n我们还可以在属性上设置监听器，从而在属性设值之前和设值之后做一些操作。对于存值属性，我们通过 `willSet` 和 `didSet` 方法监听；对于计算后属性，则可以通过 set 方法监听。\n\n```swift\nstruct SomeStruct {\n    var someVar: Int {\n        willSet(newValue) {\n            print(\"Before set new value\")\n        }\n        didSet {\n            // 旧值用 oldValue 表示\n            print(\"new value = \\(someVar), old value = \\(oldValue)\")\n        }\n    }\n}\n```\n\n##### Property Wrappers\n\n当我们定义了如何存储一个属性时，为了复用代码，我们可以将它作为模板使用。\n\n```swift\n@propertyWrapper\nstruct TwelveOrLess {\n    private var number = 0\n    var wrappedValue: Int {\n        get { return number }\n        set { number = min(newValue, 12) }\n    }\n}\n```\n\n比如以上定义了一个限制最大值的 property wrapper 之后，可以这样使用：\n\n```swift\nstruct SmallRectangle {\n    @TwelveOrLess var height: Int\n    @TwelveOrLess var width: Int\n}\n```\n\n这样，使用了该 property wrapper 的属性最大值会被限制为 12。\n\n我们也可以在 property wrapper 上设置初始化值：\n\n```swift\n@propertyWrapper\nstruct SmallNumber {\n    private var maximum: Int\n    private var number: Int\n\n    var wrappedValue: Int {\n        get { return number }\n        set { number = min(newValue, maximum) }\n    }\n\n    init() {\n        maximum = 12\n        number = 0\n    }\n  \n    init(wrappedValue: Int) {\n        maximum = 12\n        number = min(wrappedValue, maximum)\n    }\n  \n    init(wrappedValue: Int, maximum: Int) {\n        self.maximum = maximum\n        number = min(wrappedValue, maximum)\n    }\n}\n```\n\n通过添加构造器方法，使得我们可以指定初始值。\n\n```swift\nstruct MixedRectangle {\n    @SmallNumber(maximum: 20) var width: Int = 2\n    @SmallNumber var height: Int = 1\n}\n```\n\n##### Global and Local Variables\n\n我们可以像属性一样设置全局变量，不过全局变量总是会被延迟初始化。我们也可以为全局变量或者本地变量设置监听器，但是不可以为全局变量或计算后变量设置 property wrapper。\n\n```swift\nvar globalVariable: String = \"Swift\"\n\nfunc gloablFunc() {\n    @SmallNumber var version: Int = 5\n    print(\"\\(globalVariable) \\(version)\")\n}\n```\n\n##### Type Properties\n\n类型属性也就是静态属性，不需要通过创建实例而是可以直接通过类型访问的属性。\n\n#### Methods\n\n方法分为实例方法 (*instance method*) 和类型方法 (*type methods*)，类型方法也就是 Objective-C 中的类方法。相比 C 和 Objective-C，Swift 最大的不同在于，除了 `class` 之外，`struct` 和 `enum` 类中也能定义方法。\n\n##### 实例方法\n\n```swift\nfunc getResolution() -\u003e String {\n  return \"width: \\(width), height: \\(height)\"\n}\n```\n\n##### 类型方法\n\n在 Swift 中，同样使用 `static` 关键字声明类型方法，对于类而言，还可以使用 `class func` 关键字，这样子类就可以继承和覆写父类中的类型方法了。\n\n```swift\nclass SomeClass {\n    class func someTypeMethod() {\n        // do something\n    }\n}\n\nstruct SomeStruct {\n\t\tstatic func someTypeMethod() {\n        // do something\n    }\n}\n```\n\n#### Subscripts\n\nSubscript 是创建对集合、列表等的成员进行访问和赋值的语法。我们能够通过下标访问数组、字典等都是因为其内部实现了 subscript 方法。\n\n我们可以在任意的类、结构或枚举类中使用 subscript，其语法如下：\n\n```swift\nsubscript(index: Int) -\u003e Int {\n    get {\n        // 返回值\n    }\n    set(newValue) {\n        // 赋值\n    }\n}\n```\n\n`subcript` 可以指定任意的参数和任意类型的返回值，也可以为参数设置默认值和使用可变参数列表，但是不可以使用 `inout` 参数。\n\n##### Type Subscript\n\n类型 subscript 和类型方法相似，是直接作用于类型之上的方法。\n\n```swift\nenum Planet: Int {\n    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune\n\n    // 添加类型 subscript，直接通过下标创建枚举类实例\n    static subscript(n: Int) -\u003e Planet {\n        return Planet(rawValue: n)!\n    }\n}\n\nvar mars = Planet[4]\n```\n\n#### Initialization\n\n`class` 和 `struct` 的构造器方法是 `init`：\n\n```swift\ninit(argumentLabel parameterName: Type, ...) {\n    // 初始化常量和属性等\n  \tself.someValue = someValue\n}\n```\n\n如果初始化参数标签和属性名相同，可以使用 `self` 引用类中的属性，类似于其他语言中的 `this`。\n\n##### `required` init\n\n我们可以将某个构造器方法标记为 `required`：\n\n```swift\nclass SomeClass {\n    required init(param: Type) {\n        // initializer implementation goes here\n    }\n}\n```\n\n这样的话，子类就必须实现它：\n\n```swift\nclass SomeSubclass: SomeClass {\n    required init(param: Type) {\n        // subclass implementation of the required initializer goes here\n    }\n}\n```\n\n#### Deinitialization\n\n类相比 `struct` 还多了一个销毁的方法 `deinit`，在类实例被销毁之前被调用：\n\n```swift\ndeinit {\n    // 释放资源等\n}\n```\n\n销毁方法会自动调用父类销毁方法，即使子类没有定义销毁方法也一样，所以我们不用担心父类销毁方法得不到调用。\n\n#### Type Casting\n\nSwift 中的类型检查同样使用 `is` 关键字：\n\n```swift\nif instance is Type {\n    print(\"It's the type\")\n}\n\nif !(instance is Type) {\n\t\tprint(\"Not the type\")\n}\n```\n\n向下转型则使用 `as?` 和 `as!`：\n\n```swift\n// optional 转换\nif let movie = item as? Movie {\n    print(\"Movie: \\(movie.name), dir. \\(movie.director)\")\n}\n\n// 强制类型转换\nvar song = item as! Song\n```\n\n#### Nested Types\n\n我们可以在类、枚举类型、结构中定义嵌套类型。\n\n```swift\nstruct Manager {\n    enum JobTitle {\n        case CEO, CFO, CTO, CMO, CHRO, CCO\n        case vacant\n    }\n  \t\n    let name: String = \"\"\n  \t\n  \tvar jobTitle = JobTitle.vacant\n  \n    func attendMeeting(meetingName: String) {\n        print(\"\\(name)(\\(jobTitle)) is attending \u003c\\(meetingName)\u003e meeting.\")\n    }\n}\n```\n\n### Concurrency\n\n并发是个复杂的话题，所以这里不会详细展开。Swift 中的并发用法和 Kotlin 或 Dart 基本一致，背后都是基于 thread 实现，我们只需要使用几个简单的关键字就能实现同步和异步执行代码。\n\n```swift\nstatic func fetchUserId() async -\u003e Int {\n    print(\"fetching user id...\")\n    try? await Task.sleep(nanoseconds: 1000_000_000)\n    return Int.random(in: 1..\u003c5)\n}\n\nstatic func fetchUserName(userId: Int) async -\u003e String {\n    print(\"fetching user name...\")\n    try? await Task.sleep(nanoseconds: 1000_000_000)\n    return \"User\\(userId)\"\n}\n\npublic static func main() async {\n    let userId = await fetchUserId()\n    async let userName = fetchUserName(userId: userId)\n    print(\"User name = \\(await userName)\")\n}\n```\n\n### Protocols\n\n和 Objective-C 一样，Swift 中也有 protocol 的概念，我们用它来定义一组功能，然后交给子类实现，类似于其他语言中的接口概念。在 Swift 中，`enum` / `struct` / `class` 都可以实现 protocol 来增强自身的功能。\n\n语法：\n\n```swift\nprotocol SomeProtocol {\n\t  static var someTypeProperty: Type { get set }\n  \n    var mustBeSettable: Type { get set }\n    var doesNotNeedToBeSettable: Type { get }\n  \n    init(parameterName: Type)\n  \n  \tstatic func someTypeMethod(argumentLabel parameterName: Type) -\u003e returnValue\n  \n  \tfunc someMethod(argumentLabel parameterName: Type) -\u003e returnValue\n}\n```\n\n实现：\n\n```swift\nclass SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {\n    // 实现属性和方法\n}\n```\n\n#### Mutate 方法\n\n对于值类型 (*struct \u0026 enum*)，假如想要改变属性或自身的实例，需要在方法前添加 `mutating` 关键字。\n\n假如你确定 protocol 中的某个方法有可能会改变值且有可能会被值类型使用，则可以用 `mutating` 修饰：\n\n```swift\nprotocol Togglable {\n    mutating func toggle()\n}\n```\n\n子类：\n\n```swift\nenum OnOffSwitch: Togglable {\n    case off, on\n    // 如果是被 class 实现则不需要写 mutating\n    mutating func toggle() {\n        switch self {\n        case .off:\n            self = .on\n        case .on:\n            self = .off\n        }\n    }\n}\n```\n\n##### 初始化方法\n\nprotocol 中的初始化方法都是 `required` 的，子类必须实现：\n\n```swift\n// 假如 protocol 中定义了 init() 方法\nclass SomeClass: SomeProtocol {\n    required init() {\n    }\n}\n```\n\n假如子类实现同时继承了父类和实现了 protocol，则需要使用 `required override` 关键字：\n\n```swift\nclass SomeSubClass: SomeSuperClass, SomeProtocol {\n    // \"required\" from SomeProtocol conformance; \"override\" from SomeSuperClass\n    required override init() {\n        // initializer implementation goes here\n    }\n}\n```\n\n### Extensions\n\nSwift 中的 `extension` 类似于 Objective-C 中的 category，用于扩展 `enum` / `struct` / `class` / `protocol` 的功能，比如：\n\n- 增加计算后实例变量和类型变量（*computed properties*）\n- 增加实例方法 (*instance methods*) 和类型方法 (*type methods*)\n- 添加新的构造器方法 (*initializers*)\n- 添加 `subcript` 方法\n- 添加和使用新的嵌套类型 (*nested types*)\n- 实现新的 `protocol`\n\n增加实例变量和实例方法的例子：\n\n```swift\nextension VendingMachine {\n    // computed instance property\n    var singleSongPrice: Int {\n        return 10\n    }\n\n    /// instance method\n    func playSong(name: String) throws {\n        guard coinsDeposited \u003e singleSongPrice  else {\n            throw VendingMachineError.insufficientFunds(coinsNeeded: singleSongPrice)\n        }\n\n        // access instance properties and methods\n        coinsDeposited -= 5\n\n        refundRemaining()\n\n        print(\"Thanks for your coins, now playing \\(name)...\")\n    }\n}\n```\n\n注意，添加新的实例变量必须是 computed properties。\n\n### Generics\n\nSwift 中同样可以使用泛型。\n\n#### 泛型方法\n\n```swift\nfunc swapTwoValues\u003cT\u003e(_ a: inout T, _ b: inout T) {\n    let temp = a\n    a = b\n    b = temp\n}\n```\n\n使用：\n\n```swift\nvar var1 = \"var1\"\nvar var2 = \"var2\"\nswapTwoValues(var1, var2)\nprint(\"var1 = \\(var1), var2 = \\(var2)\")\n```\n\n#### 类型限制\n\n我们可以为泛型设置类型限制 (*Type Constraints*) 来明确泛型的使用边界。\n\n```swift\nfunc someFunc\u003cT: SomeClass, M: SomeProtocol\u003e(someT: T, someM: M) {\n    someT.doSomething()\n    someM.doSomething()\n}\n```\n\n#### Associated Types\n\n在定义 protocol 时，我们有时会需要定义一个相关的数据类型，类似于泛型类中的泛型数据类型。\n\n```swift\nprotocol Container {\n    associatedtype Item\n    mutating func append(_ item: Item)\n    var count: Int { get }\n    subscript(i: Int) -\u003e Item { get }\n}\n```\n\n上面定义了 `Container` 的 protocol，内部使用 `associatedtype` 定义了一个 Item 类作为关联类型，并且部分方法中使用到该类型。接下来我们定一个实现类：\n\n```swift\nstruct IntStack: Container {\n    // 指定 Container 中的 Item 类型为 Int\n    typealias Item = Int\n  \n    var items: [Int] = []\n  \n    mutating func push(_ item: Int) {\n        items.append(item)\n    }\n    mutating func pop() -\u003e Int {\n        return items.removeLast()\n    }\n  \n    mutating func append(_ item: Int) {\n        self.push(item)\n    }\n  \n    var count: Int {\n        return items.count\n    }\n  \n    subscript(i: Int) -\u003e Int {\n        return items[i]\n    }\n}\n```\n\n当然，我们也可以使用泛型类来实现 `Container`：\n\n```swift\n// 标准库中 Stack 的实现\nstruct Stack\u003cElement\u003e: Container {\n    var items: [Element] = []\n    mutating func push(_ item: Element) {\n        items.append(item)\n    }\n    mutating func pop() -\u003e Element {\n        return items.removeLast()\n    }\n    mutating func append(_ item: Element) {\n        self.push(item)\n    }\n    var count: Int {\n        return items.count\n    }\n    subscript(i: Int) -\u003e Element {\n        return items[i]\n    }\n}\n```\n\n另外，在关联类型上也可以添加类型限制。\n\n```swift\nassociatedtype Item: Equatable\n```\n\n最后，关于泛型上 `where` 关键字的使用，请看[文档](https://docs.swift.org/swift-book/LanguageGuide/Generics.html)。\n\n#### Opaque Types\n\n我们有时可能不想要返回具体的类型，此时就需要用到模糊类型。比如下面的例子：\n\n```swift\nprotocol Shape {\n    func draw() -\u003e String\n}\n\nstruct Triangle: Shape {\n    var size: Int\n    func draw() -\u003e String {\n        var result: [String] = []\n        for length in 1...size {\n            result.append(String(repeating: \"*\", count: length))\n        }\n        return result.joined(separator: \"\\n\")\n    }\n}\n\nstruct Square: Shape {\n    var size: Int\n    func draw() -\u003e String {\n        let line = String(repeating: \"*\", count: size)\n        let result = Array\u003cString\u003e(repeating: line, count: size)\n        return result.joined(separator: \"\\n\")\n    }\n}\n\nstruct JoinedShape\u003cT: Shape, U: Shape\u003e: Shape {\n    var top: T\n    var bottom: U\n    func draw() -\u003e String {\n        return top.draw() + \"\\n\" + bottom.draw()\n    }\n}\n```\n\n我们定义了一个 `Shape` protocol，其中主要包含一个 draw 方法用来表示绘制自身形状，然后实现了两个形状类以及一个由两个形状拼接而成的合成形状。接下来我们定义一个使用这些形状的方法：\n\n```swift\nfunc makeTrapezoid() -\u003e some Shape {\n    let top = Triangle(size: 2)\n    let bottom = Square(size: 2)\n    let joinedShape = JoinedShape(\n        top: top,\n        bottom: bottom\n    )\n    return joinedShape\n}\n```\n\n上面的例子中，我们使用了 `some` 关键字，这样我们就对外部隐藏了具体的形状类型，但是并不影响调用 `draw` 方法。\n\n```swift\nlet trapezoid = makeTrapezoid()\ntrapezoid.draw()\n```\n\n### Error Handling\n\nSwift 中的异常用 `Error` 代表，它是一个内容为空的 protocol，我们可以使用它来自定义我们的异常，通常使用 `enum` 类来实现。\n\n```swift\nenum VendingMachineError: Error {\n    case invalidSelection\n    case insufficientFunds(coinsNeeded: Int)\n    case outOfStock\n}\n```\n\n抛出异常的语法：\n\n```swift\nthrow VendingMachineError.insufficientFunds(coinsNeeded: 5)\n```\n\n在 Swift 中，当遇到一个异常时，我们有 4 种处理方式：\n\n- 向外传递异常 `throws`\n- 使用 `do..catch` 语法捕捉异常\n- 将异常作为可选值处理 `try?`\n- 判定异常不会出现 `try!`\n\n下面一一介绍。\n\n#### 向外传递异常\n\n当某个方法不去处理异常时，可以使用 `throws` 关键字将异常向外抛出：\n\n```swift\nfunc vend(name: String) throws {\n\t\tguard let item = inventory[name] else {\n        throw VendingMachineError.invalidSelection\n    }\n  \n  \t...\n}\n```\n\n#### 捕捉异常\n\n语法：\n\n```swift\ndo {\n    try expression\n    statements\n} catch pattern 1 {\n    statements\n} catch pattern 2 where condition {\n    statements\n} catch pattern 3, pattern 4 where condition {\n    statements\n} catch {\n    statements\n}\n```\n\n例子：\n\n```swift\ndo {\n    try vendingMachine.vend(name: name)\n    print(\"Enjoy your \\(name)!\")\n} catch {\n    // 捕捉任何异常，使用 error 获取异常类\n    print(\"Something went wrong: \\(error)\")\n}\n```\n\n#### 用可选值接收\n\n当我们只是想要获取结果时，可以不对有可能抛出的异常进行处理，而是用可选值接收：\n\n```swift\nlet result = try? funcMightThrows()\n```\n\n上面使用 `try?` 调用了一个有可能抛出异常的方法，调用成功则会获得结果，抛出异常则结果为 `nil`。\n\n#### 判断异常不会出现\n\n当我们确信不会抛出异常时，可以强制完成调用，将异常放到运行时抛出。\n\n```swift\nlet photo = try! loadImage(atPath: \"./Resources/John Appleseed.jpg\")\n```\n\n#### `defer` 表达式\n\n当一个方法有可能会抛出异常时，我们可能想要在异常抛出时执行一些操作，比如打开一个文件流后，如果执行过程中出现异常，我们会想要及时关闭文件流，此时可以使用 `defer` 表达式：\n\n```swift\nfunc processFile(filename: String) throws {\n    if exists(filename) {\n        let file = open(filename)\n        defer {\n            close(file)\n        }\n        while let line = try file.readline() {\n            // Work with the file.\n        }\n        // close(file) is called here, at the end of the scope.\n    }\n}\n```\n\n一个方法中可以有多个 `defer` 表达式，但是执行顺序是从下往上的，也就是最后定义的 `defer` 表达式最先执行。而且即使不抛出异常的方法，依然可以使用 `defer` 表达式。\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fajiew%2Fheadfirstswift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fajiew%2Fheadfirstswift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fajiew%2Fheadfirstswift/lists"}