├── README.md ├── SwiftWT.playground ├── Pages │ ├── Index.xcplaygroundpage │ │ ├── Resources │ │ │ ├── swift-icon.png │ │ │ ├── swift-icon@2x.png │ │ │ └── swift-icon@3x.png │ │ └── Contents.swift │ ├── Initialization.xcplaygroundpage │ │ └── Resources │ │ │ └── initializerDelegation01_2x.png │ ├── Type System.xcplaygroundpage │ │ └── Contents.swift │ ├── Subscripts.xcplaygroundpage │ │ └── Contents.swift │ ├── Optional Chaining.xcplaygroundpage │ │ └── Contents.swift │ ├── Nested Types.xcplaygroundpage │ │ └── Contents.swift │ ├── Methods.xcplaygroundpage │ │ └── Contents.swift │ ├── Deinitialization.xcplaygroundpage │ │ └── Contents.swift │ ├── Inheritance.xcplaygroundpage │ │ └── Contents.swift │ ├── Basic Operators.xcplaygroundpage │ │ └── Contents.swift │ ├── Structures and Classes.xcplaygroundpage │ │ └── Contents.swift │ ├── Advanced Operators.xcplaygroundpage │ │ └── Contents.swift │ ├── Automatic Reference Counting.xcplaygroundpage │ │ └── Contents.swift │ ├── Closures.xcplaygroundpage │ │ └── Contents.swift │ ├── Memory Safety.xcplaygroundpage │ │ └── Contents.swift │ ├── Error Handling.xcplaygroundpage │ │ └── Contents.swift │ ├── Enumerations.xcplaygroundpage │ │ └── Contents.swift │ ├── Pattern Matching.xcplaygroundpage │ │ └── Contents.swift │ ├── Functions.xcplaygroundpage │ │ └── Contents.swift │ ├── Type Casting.xcplaygroundpage │ │ └── Contents.swift │ ├── Extensions.xcplaygroundpage │ │ └── Contents.swift │ ├── Properties.xcplaygroundpage │ │ └── Contents.swift │ ├── Collection Types.xcplaygroundpage │ │ └── Contents.swift │ ├── Control Flow.xcplaygroundpage │ │ └── Contents.swift │ ├── Dynamic.xcplaygroundpage │ │ └── Contents.swift │ ├── Strings and Characters.xcplaygroundpage │ │ └── Contents.swift │ ├── Generics.xcplaygroundpage │ │ └── Contents.swift │ ├── Access Control.xcplaygroundpage │ │ └── Contents.swift │ ├── Protocols.xcplaygroundpage │ │ └── Contents.swift │ └── Basics.xcplaygroundpage │ │ └── Contents.swift └── contents.xcplayground ├── .git-commit.yml ├── .gitignore └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | # SwiftWT 2 | Swift Walking Through. 漫步Swift,分享Swift知识与技术~ 3 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Index.xcplaygroundpage/Resources/swift-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devedbox/SwiftWT/HEAD/SwiftWT.playground/Pages/Index.xcplaygroundpage/Resources/swift-icon.png -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Index.xcplaygroundpage/Resources/swift-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devedbox/SwiftWT/HEAD/SwiftWT.playground/Pages/Index.xcplaygroundpage/Resources/swift-icon@2x.png -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Index.xcplaygroundpage/Resources/swift-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devedbox/SwiftWT/HEAD/SwiftWT.playground/Pages/Index.xcplaygroundpage/Resources/swift-icon@3x.png -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Initialization.xcplaygroundpage/Resources/initializerDelegation01_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devedbox/SwiftWT/HEAD/SwiftWT.playground/Pages/Initialization.xcplaygroundpage/Resources/initializerDelegation01_2x.png -------------------------------------------------------------------------------- /.git-commit.yml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | # types: # defaults using (feat|fix|docs|style|refactor|test|chore) types. 3 | scope: 4 | required: false 5 | allows-ascii-punctuation: true 6 | # ignoring-pattern: # Default is nil. 7 | # ignores-hash-anchored-lines: true # Default is false. 8 | # allows-revert: true # Default is true. 9 | # ignores-trailing-new-lines: true # Default is false. -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Type System.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import Foundation 4 | 5 | //: # 类型系统 6 | /*: 7 | Swift 是一门强类型语言,在 Swift 的世界中,一切皆类型,每个变量都有确定的类型,而且每个类型也有自己的类型。Swift 的强类型特性衍生出来其他的一些特性,最常见和常用的就是我们的*类型推断*,类型推断可以通过代码的上下文信息推断出代码的执行结果的类型,这在 Swift 的代码中很常用;类型推断还可以使用在函数(属性)调用以及枚举类型上,如枚举类型可以在编写的时候省略枚举类型的声明而直接使用 `.case` 的形式来使用。 8 | 9 | Swift 中的类型系统很强大、很智能,它总是会向我们传达一个消息:“我知道你的类型”,而不是:“请告诉我你的类型”,我们在编写 OC 的代码的时候,类型总是由我们告知编译器的,但是在 Swift 中,我们总算可以解放了~ 10 | 11 | 在 Swift 中一定要铭记类型的重要性,类型贯穿了整个 Swift 的所有特性,为什么说 Swift 安全,Swift 的安全性最明确的提现就是在类型的处理上,一个 `Int` 类型的变量只接受 `Int` 类型的值,尝试传递其他类型的值是不被允许的,而且在编译期间就可以将这类问题暴露出来,不会等到运行时出现问题了毫不留情的给我们来一个`crash`。 12 | 13 | 因此,在编写 Swift 代码时一定要将思路转变过来,不要使用动态语言的思维去编写 Swift 代码,因为 Swift 是一门静态强类型语言! 14 | */ 15 | //: ## 类型推断 16 | /*: 17 | 类型推断可以在不显式指定类型的情况下由类型推断系统推断出类型,这个特性可以帮助我们编写出简洁的代码。创建一个变量: 18 | */ 19 | let intVal: Int = 1 20 | let floatVal: Float = 1 // 21 | let doubleVal: Float = 1 22 | //: 使用类型推断: 23 | let intValue = 1 24 | let floatValue = 1.0 25 | let doubleValue = 1.0 26 | //: 需要注意的是,在使用类型推断的时候,Swift 会以最大兼容性来推断数值字面量的类型,什么意思呢?浮点型字面量永远会被推断为 `Double` 类型而非 `Float` 类型。 27 | //: [Next](@next) 28 | -------------------------------------------------------------------------------- /SwiftWT.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Subscripts.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Subscripts.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 下标 10 | /* : 11 | 下标可以定义在类、结构体和枚举中,是访问集合、列表或序列中元素的快捷方式。可以使用下标的索引,设置和获取值,而不需要再调用对应的存取方法。举例来说,用下标访问一个 Array 实例中的元素可以写作 someArray[index],访问 Dictionary 实例中的元素可以写作 someDictionary[key]。 12 | 13 | 一个类型可以定义多个下标,通过不同索引类型进行重载。下标不限于一维,你可以定义具有多个入参的下标满足自定义类型的需求。 14 | */ 15 | 16 | //: ## 下标语法 17 | /*: 18 | 与定义实例方法类似,定义下标使用 subscript 关键字,指定一个或多个输入参数和返回类型;与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,有点类似计算型属性,Swift 中任意类型都可以定义下标: 19 | 20 | subscript(index: Int) -> Int { 21 | get { 22 | // 返回一个适当的 Int 类型的值 23 | } 24 | 25 | set(newValue) { 26 | // 执行适当的赋值操作 27 | } 28 | } 29 | 30 | readonly: 31 | 32 | subscript(index: Int) -> Int { 33 | // 返回一个适当的 Int 类型的值 34 | } 35 | 36 | struct TimesTable { 37 | let multiplier: Int 38 | subscript(index: Int) -> Int { 39 | return multiplier * index 40 | } 41 | } 42 | let threeTimesTable = TimesTable(multiplier: 3) 43 | print("six times three is \(threeTimesTable[6])") 44 | // 打印 "six times three is 18" 45 | 46 | */ 47 | 48 | //: ## 下标参数 49 | /*: 50 | 下标可以接受任意数量的入参,并且这些入参可以是任意类型。下标的返回值也可以是任意类型。下标可以使用变量参数和可变参数,但不能使用输入输出参数,也不能给参数设置默认值。 51 | 52 | 下标可以重载,一个类或结构体可以根据自身需要提供多个下标实现,使用下标时将通过入参的数量和类型进行区分,自动匹配合适的下标: 53 | 54 | struct Matrix { 55 | let rows: Int, columns: Int 56 | var grid: [Double] 57 | init(rows: Int, columns: Int) { 58 | self.rows = rows 59 | self.columns = columns 60 | grid = Array(repeating: 0.0, count: rows * columns) 61 | } 62 | func indexIsValid(row: Int, column: Int) -> Bool { 63 | return row >= 0 && row < rows && column >= 0 && column < columns 64 | } 65 | subscript(row: Int, column: Int) -> Double { 66 | get { 67 | assert(indexIsValid(row: row, column: column), "Index out of range") 68 | return grid[(row * columns) + column] 69 | } 70 | set { 71 | assert(indexIsValid(row: row, column: column), "Index out of range") 72 | grid[(row * columns) + column] = newValue 73 | } 74 | } 75 | } 76 | 77 | var matrix = Matrix(rows: 2, columns: 2) 78 | matrix[0, 1] = 1.5 79 | matrix[1, 0] = 3.2 80 | 81 | let someValue = matrix[2, 2] 82 | // 断言将会触发,因为 [2, 2] 已经超过了 matrix 的范围 83 | 84 | */ 85 | //: [下一页](@next) 86 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Index.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Index.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: # ![Swift-Icon](swift-icon.png) Swift Basic 8 | /*: 9 | `Swift`是`Apple`于`2014`年推出的新语言,于`2015`年开源,经过几年的发展,现在`Swift`已经越来越成熟。`Swift`不仅可以用来开发基于`CocoaTouch`的`iOS`应用,还能作为一门脚本语言和后端语言,在开源社区,已有了很多比较成熟的后端开发框架,如[`Perfect`](https://github.com/PerfectlySoft/Perfect)、[`Vapor`](https://github.com/vapor/vapor)、[`Kitura`](https://github.com/IBM-Swift/Kitura)、[`Taylor`](https://github.com/izqui/Taylor)等,但是生态发展还不成熟,而且官方的推动也需要时间;`Swift`设计就是一门脚本语言,虽然不像`Python`、`Ruby`等语言发展成熟,但是`Swift`作为脚本语言还是有很深的潜力,像现在很多工具都是`Swift`开发的,如`SwiftLint`、`Carthage`等工具. 10 | 11 | `Swift`是一门静态语言,所有类型、属性和方法都必须在编译期确定下来;`Swift`是一门强类型语言,每一个变量、实例都有自己的类型,而且对类型特别敏感,没有类型容错;`Swift`是一门高效的语言,所有方法的分发都是在编译期确定的,没有`OC`的运行时发送消息的机制,同时,这也让`Swift`变得安全,大部分错误都能在编译期暴露出来,非常适合构建大型项目,能够很大程度的提升代码的健壮性;`Swift`还支持多种编程范式,如函数式编程、泛型编程等,这些编程范式可以大大提高开发效率;但是`Swift`也有很多诟病,从`Swift` `1.0`到现在的`Swift` `4.0`,一直在更改API和添加特性,这让`Swift`变得越来越复杂和难以理解,但是,从经验来看,很多特性确实能够节省很多的开发时间,所以,还得辩证的看待这个事情. 12 | 13 | `Swift`拥有很多特性,上述已经列举部分,引用`Swift`官方文档,`Swift`是一门现代语言,体现在以下几点: 14 | - 安全. 15 | - **`Fast`**:译为高效,但是也包含字面意义的快速;对比很多脚本语言甚至主流的`Java`等语言,`Swift`在高效这个层面做得确实很到位. 16 | - 可读、易使用:`Swift`吸取了很多语言的语法和编程习惯,大杂烩. 17 | 18 | `Swift`除了支出很多编程语言最基本的东西之外,还引入了很多特性,如:模块化(去除头文件)、命名空间以及借鉴的其他语言的很多特性,这些特性可以帮助我们编写很多高效、优雅的代码,相比很多C(特别是OC)家族的语言,`Swift`还有如下特点: 19 | - 函数、闭包是一等公民 20 | - 支持元组类型和函数多返回值(`Python`) 21 | - 泛型编程:泛型协议、泛型类型、泛型方法 22 | - 函数式编程:标准库内建方法:`map`、`filter`、`reduce`等 23 | - 快速简洁的集合类型遍历语法 24 | - 结构体可以支持方法定义、类型扩展、实现协议 25 | - 强大的错误处理机制 26 | - 高级流程控制语法,如:`guard`、`do`、`defer`、`repeat` 27 | */ 28 | /*: 29 | ## 平台支持 30 | - `MacOS` 31 | - `Linux` 32 | */ 33 | /*: 34 | - Callout(Swift.org Projects): 35 | The Swift language is managed as a collection of projects, each with its own repositories. The current list of projects includes: 36 | 37 | - The [Swift compiler](https://swift.org/compiler-stdlib/) command line tool 38 | - The [standard library](https://swift.org/compiler-stdlib/) bundled as part of the language 39 | - [Core libraries](https://swift.org/core-libraries/) that provide higher-level functionality 40 | - The [LLDB debugger](https://swift.org/lldb/) which includes the Swift REPL 41 | - The [Swift package manager](https://swift.org/package-manager/) for distributing and building Swift source code 42 | - [Xcode playground support](https://swift.org/lldb/#xcode-playground-support) to enable playgrounds in Xcode. 43 | */ 44 | /*: 45 | ## 目录 46 | [`Swift`基础部分](Basics) 47 | 48 | [基本运算符](Basic%20Operators) 49 | 50 | [字符与字符串](Strings%20and%20Characters) 51 | 52 | [集合类型](Collection%20Types) 53 | 54 | [控制流](Control%20Flow) 55 | 56 | [函数](Functions) 57 | 58 | [闭包](Closures) 59 | 60 | [枚举](Enumerations) 61 | 62 | [类和结构体](Structures%20and%20Classes) 63 | 64 | [属性](Properties) 65 | 66 | [方法](Methods) 67 | 68 | [下标](Subscripts) 69 | 70 | [继承](Inheritance) 71 | 72 | [构造器](Initialization) 73 | 74 | [销毁器](Deinitialization) 75 | 76 | [可选链](Optional%20Chaining) 77 | 78 | [错误处理](Error%20Handling) 79 | 80 | [类型嵌套](Nested%20Types) 81 | 82 | [扩展](Extensions) 83 | 84 | [协议](Protocols) 85 | 86 | [泛型](Generics) 87 | 88 | [内存管理](Automatic%20Reference%20Counting) 89 | 90 | [内存安全](Memory%20Safety) 91 | 92 | [访问控制](Access%20Control) 93 | 94 | [高级运算符](Advanced%20Operators) 95 | */ 96 | //: [下一页](@next) 97 | 98 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Optional Chaining.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Optional-Chaining.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 可选链 10 | /*: 11 | 可选链是一种可以在当前值可能为 nil 的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是 nil,那么调用将返回 nil。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为 nil,整个调用链都会失败或返回 nil。 12 | 13 | Swift 的可选链和 Objective-C 中向 nil 发送消息有些相像,但是 Swift 的可选链可以应用于任意类型,并且能检查调用是否成功。 14 | */ 15 | 16 | //: ## 使用可链代替强制解包 17 | /*: 18 | 通过在想调用的属性、方法,或下标的可选值后面放一个问号(?),即可定义一个可选链。与在可选值后面放一个叹号(!)来解包很相似。主要区别在于当可选值为空时可选链调用只会调用失败,然而强制展开将会触发运行时错误。 19 | 20 | 可选链的返回结果是一个对应类型的可选值。可以利用返回值来判断可选链调用是否调用成功,如果调用有返回值则说明调用成功,返回 nil 则说明调用失败。 21 | 22 | class Person { 23 | var residence: Residence? 24 | } 25 | 26 | class Residence { 27 | var numberOfRooms = 1 28 | } 29 | 30 | let john = Person() 31 | 32 | let roomCount = john.residence!.numberOfRooms 33 | // 这会引发运行时错误 34 | 35 | 使用可选链的方式: 36 | 37 | if let roomCount = john.residence?.numberOfRooms { 38 | print("John's residence has \(roomCount) room(s).") 39 | } else { 40 | print("Unable to retrieve the number of rooms.") 41 | } 42 | // 打印 “Unable to retrieve the number of rooms.” 43 | 44 | 45 | john.residence = Residence() 46 | 47 | if let roomCount = john.residence?.numberOfRooms { 48 | print("John's residence has \(roomCount) room(s).") 49 | } else { 50 | print("Unable to retrieve the number of rooms.") 51 | } 52 | // 打印 “John's residence has 1 room(s).” 53 | */ 54 | 55 | //: ## 通过可选链式调用访问属性 56 | /*: 57 | 可以通过可选链调用在一个可选值上访问它的属性,并判断访问是否成功。 58 | 59 | let john = Person() 60 | if let roomCount = john.residence?.numberOfRooms { 61 | print("John's residence has \(roomCount) room(s).") 62 | } else { 63 | print("Unable to retrieve the number of rooms.") 64 | } 65 | // 打印 “Unable to retrieve the number of rooms.” 66 | 67 | 还可以通过可选链式调用来设置属性值: 68 | 69 | let someAddress = Address() 70 | someAddress.buildingNumber = "29" 71 | someAddress.street = "Acacia Road" 72 | john.residence?.address = someAddress 73 | 74 | */ 75 | 76 | //: ## 通过可选链式调用来调用方法 77 | /*: 78 | 可以通过可选链调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。无返回值的函数实际为 Void ,链式调用将会返回 Void?,而不是 Void。 79 | 80 | 通过判断返回值是否为 nil 可以判断调用是否成功: 81 | 82 | if john.residence?.printNumberOfRooms() != nil { 83 | print("It was possible to print the number of rooms.") 84 | } else { 85 | print("It was not possible to print the number of rooms.") 86 | } 87 | // 打印 “It was not possible to print the number of rooms.” 88 | 89 | if (john.residence?.address = someAddress) != nil { 90 | print("It was possible to set the address.") 91 | } else { 92 | print("It was not possible to set the address.") 93 | } 94 | // 打印 “It was not possible to set the address.” 95 | 96 | */ 97 | 98 | //: ## 通过可选链式调用访问下标 99 | /*: 100 | 通过可选链调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功: 101 | 102 | if let firstRoomName = john.residence?[0].name { 103 | print("The first room name is \(firstRoomName).") 104 | } else { 105 | print("Unable to retrieve the first room name.") 106 | } 107 | // 打印 “Unable to retrieve the first room name.” 108 | 109 | 可选链赋值: 110 | 111 | john.residence?[0] = Room(name: "Bathroom") 112 | 113 | */ 114 | //: [下一页](@next) 115 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Nested Types.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Nested-Types.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 嵌套类型 10 | /*: 11 | Swift 允许定义嵌套类型,可以在支持的类型中定义嵌套的枚举、类和结构体。要在一个类型中嵌套另一个类型,将嵌套类型的定义写在其外部类型的 {} 内即可,而且可以多级嵌套。 12 | */ 13 | //: ## 实例 14 | /*: 15 | struct BlackjackCard { 16 | 17 | // 嵌套的 Suit 枚举 18 | enum Suit: Character { 19 | case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣" 20 | } 21 | 22 | // 嵌套的 Rank 枚举 23 | enum Rank: Int { 24 | case two = 2, three, four, five, six, seven, eight, nine, ten 25 | case jack, queen, king, ace 26 | struct Values { 27 | let first: Int, second: Int? 28 | } 29 | var values: Values { 30 | switch self { 31 | case .ace: 32 | return Values(first: 1, second: 11) 33 | case .jack, .queen, .king: 34 | return Values(first: 10, second: nil) 35 | default: 36 | return Values(first: self.rawValue, second: nil) 37 | } 38 | } 39 | } 40 | 41 | // BlackjackCard 的属性和方法 42 | let rank: Rank, suit: Suit 43 | var description: String { 44 | var output = "suit is \(suit.rawValue)," 45 | output += " value is \(rank.values.first)" 46 | if let second = rank.values.second { 47 | output += " or \(second)" 48 | } 49 | return output 50 | } 51 | } 52 | 53 | let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades) 54 | print("theAceOfSpades: \(theAceOfSpades.description)") 55 | // 打印 “theAceOfSpades: suit is ♠, value is 1 or 11” 56 | */ 57 | struct BlackjackCard { 58 | 59 | // 嵌套的 Suit 枚举 60 | enum Suit: Character { 61 | case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣" 62 | } 63 | 64 | // 嵌套的 Rank 枚举 65 | enum Rank: Int { 66 | case two = 2, three, four, five, six, seven, eight, nine, ten 67 | case jack, queen, king, ace 68 | struct Values { 69 | let first: Int, second: Int? 70 | } 71 | var values: Values { 72 | switch self { 73 | case .ace: 74 | return Values(first: 1, second: 11) 75 | case .jack, .queen, .king: 76 | return Values(first: 10, second: nil) 77 | default: 78 | return Values(first: self.rawValue, second: nil) 79 | } 80 | } 81 | } 82 | 83 | // BlackjackCard 的属性和方法 84 | let rank: Rank, suit: Suit 85 | var description: String { 86 | var output = "suit is \(suit.rawValue)," 87 | output += " value is \(rank.values.first)" 88 | if let second = rank.values.second { 89 | output += " or \(second)" 90 | } 91 | return output 92 | } 93 | } 94 | 95 | let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades) 96 | print("theAceOfSpades: \(theAceOfSpades.description)") 97 | //: ## 引用嵌套类型 98 | /*: 99 | 在外部引用嵌套类型时,在嵌套类型的类型名前加上其外部类型的类型名作为前缀: 100 | 101 | let heartsSymbol = BlackjackCard.Suit.hearts.rawValue 102 | // 红心符号为 “♡” 103 | 104 | */ 105 | let heartsSymbol = BlackjackCard.Suit.hearts.rawValue 106 | //: [下一页](@next) 107 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Methods.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Methods.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 方法 10 | /*: 11 | 方法是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 Objective-C 中的类方法(class methods)相似。 12 | 13 | 结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活地在你创建的类型(类/结构体/枚举)上定义方法。 14 | */ 15 | 16 | //: ## 实例方法 17 | /*: 18 | 实例方法是属于某个特定类、结构体或者枚举类型实例的方法。实例方法的语法与函数完全一致: 19 | 20 | class Counter { 21 | var count = 0 22 | func increment() { 23 | count += 1 24 | } 25 | func increment(by amount: Int) { 26 | count += amount 27 | } 28 | func reset() { 29 | count = 0 30 | } 31 | } 32 | 33 | 和调用属性一样,用点语法(dot syntax)调用实例方法: 34 | 35 | let counter = Counter() 36 | // 初始计数值是0 37 | counter.increment() 38 | // 计数值现在是1 39 | counter.increment(by: 5) 40 | // 计数值现在是6 41 | counter.reset() 42 | // 计数值现在是0 43 | 44 | */ 45 | 46 | //: ## self 属性 47 | /*: 48 | 类型的每一个实例都有一个隐含属性叫做 self,self 完全等同于该实例本身。你可以在一个实例的实例方法中使用这个隐含的 self 属性来引用当前实例。在类型内部使用的时候可以省略!但是在方法参数名与属性名冲突时就不能省略,另外,在需要引用 self 的闭包(如逃逸闭包)里引用 self 时需要显式引用。 49 | 50 | struct Point { 51 | var x = 0.0, y = 0.0 52 | func isToTheRightOfX(_ x: Double) -> Bool { 53 | return self.x > x 54 | } 55 | } 56 | let somePoint = Point(x: 4.0, y: 5.0) 57 | if somePoint.isToTheRightOfX(1.0) { 58 | print("This point is to the right of the line where x == 1.0") 59 | } 60 | // 打印 "This point is to the right of the line where x == 1.0" 61 | 62 | */ 63 | 64 | //: ## 在实例方法中修改值类型 65 | /*: 66 | 结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。但是可以为方法添加 可变(mutating)行为,然后就可以从其方法内部改变它的属性;这时,方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的 self 属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例: 67 | 68 | 要使用 可变方法,将关键字 mutating 放到方法的 func 关键字之前就可以了: 69 | 70 | struct Point { 71 | var x = 0.0, y = 0.0 72 | mutating func moveByX(_ deltaX: Double, y deltaY: Double) { 73 | x += deltaX 74 | y += deltaY 75 | } 76 | } 77 | var somePoint = Point(x: 1.0, y: 1.0) 78 | somePoint.moveByX(2.0, y: 3.0) 79 | print("The point is now at (\(somePoint.x), \(somePoint.y))") 80 | // 打印 "The point is now at (3.0, 4.0)" 81 | 82 | struct Point { 83 | var x = 0.0, y = 0.0 84 | mutating func moveBy(x deltaX: Double, y deltaY: Double) { 85 | self = Point(x: x + deltaX, y: y + deltaY) 86 | } 87 | } 88 | 89 | enum TriStateSwitch { 90 | case Off, Low, High 91 | mutating func next() { 92 | switch self { 93 | case .Off: 94 | self = .Low 95 | case .Low: 96 | self = .High 97 | case .High: 98 | self = .Off 99 | } 100 | } 101 | } 102 | var ovenLight = TriStateSwitch.Low 103 | ovenLight.next() 104 | // ovenLight 现在等于 .High 105 | ovenLight.next() 106 | // ovenLight 现在等于 .Off 107 | 108 | */ 109 | 110 | //: ## 类型方法 111 | /*: 112 | 定义在类型本身上调用的方法,这种方法就叫做类型方法。在方法的 func 关键字之前加上关键字 static,来指定类型方法。类还可以用关键字 class 来允许子类重写父类的方法实现;类型方法中的 self 指向类型本身: 113 | 114 | class SomeClass { 115 | class func someTypeMethod() { 116 | // 在这里实现类型方法 117 | } 118 | } 119 | SomeClass.someTypeMethod() 120 | 121 | */ 122 | //: [下一页](@next) 123 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Deinitialization.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Deinitialization.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 析构过程 10 | /*: 11 | 析构器只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字 deinit 来标示,类似于构造器要用 init 来标示。 12 | */ 13 | //: ## 析构过程原理 14 | /*: 15 | Swift 会自动释放不再需要的实例以释放资源,Swift 通过自动引用计数(ARC) 处理实例的内存管理,当实例被释放时不需要手动地去清理;但是,当使用自己的资源时,可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。 16 | 17 | 在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数,如下所示: 18 | 19 | deinit { 20 | // 执行析构过程 21 | } 22 | 23 | 析构器会在实例释放发生前被自动调用,不能主动调用析构器。子类继承了父类的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用。 24 | 25 | class Bank { 26 | static var coinsInBank = 10_000 27 | static func distribute(coins numberOfCoinsRequested: Int) -> Int { 28 | let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank) 29 | coinsInBank -= numberOfCoinsToVend 30 | return numberOfCoinsToVend 31 | } 32 | static func receive(coins: Int) { 33 | coinsInBank += coins 34 | } 35 | } 36 | 37 | class Player { 38 | var coinsInPurse: Int 39 | init(coins: Int) { 40 | coinsInPurse = Bank.distribute(coins: coins) 41 | } 42 | func win(coins: Int) { 43 | coinsInPurse += Bank.distribute(coins: coins) 44 | } 45 | deinit { 46 | Bank.receive(coins: coinsInPurse) 47 | } 48 | } 49 | 50 | var playerOne: Player? = Player(coins: 100) 51 | print("A new player has joined the game with \(playerOne!.coinsInPurse) coins") 52 | // 打印 "A new player has joined the game with 100 coins" 53 | print("There are now \(Bank.coinsInBank) coins left in the bank") 54 | // 打印 "There are now 9900 coins left in the bank" 55 | 56 | playerOne!.win(coins: 2_000) 57 | print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins") 58 | // 输出 "PlayerOne won 2000 coins & now has 2100 coins" 59 | print("The bank now only has \(Bank.coinsInBank) coins left") 60 | // 输出 "The bank now only has 7900 coins left" 61 | 62 | playerOne = nil 63 | print("PlayerOne has left the game") 64 | // 打印 "PlayerOne has left the game" 65 | print("The bank now has \(Bank.coinsInBank) coins") 66 | // 打印 "The bank now has 10000 coins" 67 | */ 68 | class Bank { 69 | static var coinsInBank = 10_000 70 | static func distribute(coins numberOfCoinsRequested: Int) -> Int { 71 | let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank) 72 | coinsInBank -= numberOfCoinsToVend 73 | return numberOfCoinsToVend 74 | } 75 | static func receive(coins: Int) { 76 | coinsInBank += coins 77 | } 78 | } 79 | 80 | class Player { 81 | var coinsInPurse: Int 82 | init(coins: Int) { 83 | coinsInPurse = Bank.distribute(coins: coins) 84 | } 85 | func win(coins: Int) { 86 | coinsInPurse += Bank.distribute(coins: coins) 87 | } 88 | deinit { 89 | Bank.receive(coins: coinsInPurse) 90 | } 91 | } 92 | 93 | var playerOne: Player? = Player(coins: 100) 94 | print("A new player has joined the game with \(playerOne!.coinsInPurse) coins") 95 | print("There are now \(Bank.coinsInBank) coins left in the bank") 96 | 97 | playerOne!.win(coins: 2_000) 98 | print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins") 99 | print("The bank now only has \(Bank.coinsInBank) coins left") 100 | 101 | playerOne = nil 102 | print("PlayerOne has left the game") 103 | print("The bank now has \(Bank.coinsInBank) coins") 104 | //: [下一页](@next) 105 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Inheritance.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Inheritance.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 继承 10 | /*: 11 | 一个类可以继承另一个类的方法,属性和其它特性。在 Swift 中,继承是区分「类」与其它类型的一个基本特征。 12 | 13 | 在 Swift 中,类可以调用和访问父类的方法、属性和下标,并且可以重写这些方法、属性和下标来优化或修改它们的行为。Swift 会检查重写定义在父类中是否有匹配的定义,以确保重写是正确的。 14 | 15 | 可以为类中继承来的属性添加属性观察器,这样,当属性值改变时,类就会收到通知;可以为任何属性添加属性观察器,存储型属性和计算型属性均可。 16 | 17 | */ 18 | 19 | //: ## 基类 20 | /*: 21 | 不继承于其它类的类,称为基类: 22 | 23 | class Vehicle { 24 | var currentSpeed = 0.0 25 | var description: String { 26 | return "traveling at \(currentSpeed) miles per hour" 27 | } 28 | func makeNoise() { 29 | // 什么也不做-因为车辆不一定会有噪音 30 | } 31 | } 32 | 33 | - Note: 34 | Swift 中的类并不是从一个通用的基类(如 `NSObject` )继承而来。如果不为定义的类指定一个父类的话,这个类就自动成为基类。 35 | */ 36 | 37 | //: ## 子类派生 38 | /*: 39 | 指定某个类的父类,只需将父类名写在子类名的后面,用冒号分隔即可: 40 | 41 | class SomeClass: SomeSuperclass { 42 | // 这里是子类的定义 43 | } 44 | 45 | 举个例子: 46 | 47 | class Bicycle: Vehicle { 48 | var hasBasket = false 49 | } 50 | 51 | let bicycle = Bicycle() 52 | bicycle.hasBasket = true 53 | 54 | bicycle.currentSpeed = 15.0 55 | print("Bicycle: \(bicycle.description)") 56 | // 打印 "Bicycle: traveling at 15.0 miles per hour" 57 | 58 | */ 59 | 60 | //: ## Override 61 | /*: 62 | 子类可以为继承来的实例方法,类方法,实例属性,或下标提供自己定制的实现。重写某个特性,需要在重写定义的前面加上 override 关键字。意外的重写行为可能会导致不可预知的错误,任何缺少 override 关键字的重写都会在编译时被报错。 63 | 64 | ### 访问父类的方法,属性及下标 65 | 通过使用 super 前缀来访问父类版本的方法,属性或下标: 66 | - 通过 super.someMethod() 来调用父类版本的 someMethod() 方法。 67 | - 通过 super.someProperty 来访问父类版本的 someProperty 属性。 68 | - 通过 super[someIndex] 来访问父类版本中的相同下标。 69 | 70 | */ 71 | 72 | //: ### 重写方法 73 | /*: 74 | 在子类中,你可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现: 75 | 76 | class Train: Vehicle { 77 | override func makeNoise() { 78 | print("Choo Choo") 79 | } 80 | } 81 | 82 | let train = Train() 83 | train.makeNoise() 84 | // 打印 "Choo Choo" 85 | 86 | */ 87 | 88 | //: ### 重写属性 89 | /*: 90 | 可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter,或添加属性观察器使重写的属性可以观察属性值什么时候发生改变。 91 | */ 92 | //: #### 重写属性的 Getters 和 Setters 93 | /*: 94 | 提供定制的 getter(或 setter)来重写任意继承来的属性,重写一个属性时,必需将它的名字和类型都写出来。 95 | 96 | 可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。 97 | 98 | class Car: Vehicle { 99 | var gear = 1 100 | override var description: String { 101 | return super.description + " in gear \(gear)" 102 | } 103 | } 104 | 105 | let car = Car() 106 | car.currentSpeed = 25.0 107 | car.gear = 3 108 | print("Car: \(car.description)") 109 | // 打印 "Car: traveling at 25.0 miles per hour in gear 3" 110 | 111 | - Note: 112 | 如果你在重写属性中提供了 setter,那么你也一定要提供 getter。 113 | */ 114 | //: #### 重写属性观察器 115 | /*: 116 | 可以通过重写属性为一个继承来的属性添加属性观察器: 117 | 118 | class AutomaticCar: Car { 119 | override var currentSpeed: Double { 120 | didSet { 121 | gear = Int(currentSpeed / 10.0) + 1 122 | } 123 | } 124 | } 125 | 126 | let automatic = AutomaticCar() 127 | automatic.currentSpeed = 35.0 128 | print("AutomaticCar: \(automatic.description)") 129 | // 打印 "AutomaticCar: traveling at 35.0 miles per hour in gear 4" 130 | 131 | - Note: 132 | 需要注意,不可以同时提供重写的 setter 和重写的属性观察器。 133 | */ 134 | //: ## 防止Override 135 | /*: 136 | 通过把方法,属性或下标标记为final来防止它们被重写,只需要在声明关键字前加上 final 修饰符即可(例如:final var,final func,final class func,以及 final subscript)。 137 | 138 | 通过在关键字 class 前添加 final 修饰符(final class)来将整个类标记为 final 的。这样的类是不可被继承的。 139 | */ 140 | //: [下一页](@next) 141 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Basic Operators.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Basic-Operators.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 基本运算符 10 | /*: 11 | `Swift`包含了大部分语言都有的运算符,如基本的算术运算符、比较运算符、逻辑运算符等,和其他语言不一样,`Swift`中赋值运算符默认没有返回,这样就避免了`==`与`=`运算符使用不当的场景;`Swift`的算术运算符会检测运算值并避免值溢出;除此之外,`Swift`还支持自定义运算符以及运算符重载,详见[高级运算符](Advanced%20Operators). 12 | 13 | let maxInt8: Int8 = Int8.max 14 | let overflowInt8: Int8 = Int8.max + maxInt8 // Error: Arithmetic operation '127 + 127' (on type 'Int8') results in an overflow 15 | */ 16 | 17 | //: ## 基础 18 | /*: 19 | - 赋值运算符:`=` 20 | - 复合赋值运算符:`+=`、`-=` 21 | - 算术运算符:`+`、`-`、`*`、`/`、`%` 22 | - 正负运算符:`+`、`-` 23 | 24 | - Note: 25 | 自增自减运算符自`Swift` `3.0`之后就被移除了 26 | */ 27 | 28 | //: ## 比较运算符 29 | /*: 30 | - 等于:`==` 31 | - 不等于:`!=` 32 | - 大于:`>` 33 | - 小于:`<` 34 | - 大于等于:`>=` 35 | - 小于等于:`<=` 36 | 37 | - Note: 38 | `Swift`比较类实例是否是同一个引用需要使用恒等运算符:`===`、`!==` 39 | */ 40 | 41 | //: ## 逻辑运算符 42 | /*: 43 | - 逻辑与:`&&` 44 | - 逻辑非:`!` 45 | - 逻辑或:`||` 46 | */ 47 | 48 | //: ## 位运算符 49 | /*: 50 | - 按位取反:`~` 51 | - 按位与:`&` 52 | - 按位或:`|` 53 | - 按位异或:`^` 54 | - 按位右移:`>>` 55 | - 按位左移:`<<` 56 | */ 57 | 58 | //: ## 溢出运算符 59 | /*: 60 | - 溢出加:`&+` 61 | - 溢出减:`&-` 62 | - 溢出乘:`&-` 63 | 64 | 上溢出: 65 | 66 | var unsignedOverflow = UInt8.max 67 | // unsignedOverflow 等于 UInt8 所能容纳的最大整数 255 68 | unsignedOverflow = unsignedOverflow &+ 1 69 | // 此时 unsignedOverflow 等于 0 70 | 71 | 下溢出: 72 | 73 | var unsignedOverflow = UInt8.min 74 | // unsignedOverflow 等于 UInt8 所能容纳的最小整数 0 75 | unsignedOverflow = unsignedOverflow &- 1 76 | // 此时 unsignedOverflow 等于 255 77 | 78 | */ 79 | 80 | //: ## 三元运算符 81 | /*: 82 | 三元条件运算符,和`C`语言一样,语法为:`true` `?` `yes` `:` `no`,三元运算符是以下代码的缩写形式: 83 | 84 | if question { 85 | answer1 86 | } else { 87 | answer2 88 | } 89 | */ 90 | 91 | //: ## 空合运算符 92 | /*: 93 | `Swift`特有的运算符,空合运算符的作用是为可选的变量提供一个默认值,空合运算符的语法:`optionalValue` `??` `defaultValue`,等效于`optionValue` `==` `nil` `?` `optionalValue!` `:` `defaultValue`: 94 | 95 | let defaultColorName = "red" 96 | var userDefinedColorName: String? //默认值为 nil 97 | 98 | var colorNameToUse = userDefinedColorName ?? defaultColorName 99 | // userDefinedColorName 的值为空,所以 colorNameToUse 的值为 "red" 100 | - Note: 101 | 当`optionalValue`不为`nil`时,`defaultValue`不会被求值,这既是所谓的**短路求值**。 102 | */ 103 | 104 | //: ## 区间运算符 105 | /*: 106 | 区间运算符可以表示一个连续的数值区间,表示一个范围. 107 | */ 108 | //: ### 闭区间运算符 109 | /*: 110 | 闭区间运算符(a...b)定义一个包含从 a 到 b(包括 a 和 b)的所有值的区间。a 的值不能超过 b。 111 | 112 | 闭区间运算符在迭代一个区间的所有值时是非常有用的,如在 for-in 循环中: 113 | 114 | for index in 1...5 { 115 | print("\(index) * 5 = \(index * 5)") 116 | } 117 | // 1 * 5 = 5 118 | // 2 * 5 = 10 119 | // 3 * 5 = 15 120 | // 4 * 5 = 20 121 | // 5 * 5 = 25 122 | */ 123 | //: ### 半开区间运算符 124 | /*: 125 | 半开区间运算符(a.. Vector2D { 30 | return Vector2D(x: left.x + right.x, y: left.y + right.y) 31 | } 32 | } 33 | 34 | let vector = Vector2D(x: 3.0, y: 1.0) 35 | let anotherVector = Vector2D(x: 2.0, y: 4.0) 36 | let combinedVector = vector + anotherVector 37 | // combinedVector 是一个新的 Vector2D 实例,值为 (5.0, 5.0) 38 | 39 | */ 40 | 41 | //: ## 前缀和后缀运算符 42 | /*: 43 | 类与结构体也能提供标准单目运算符的实现。单目运算符只运算一个值。当运算符出现在值之前时,它就是前缀的(例如 -a),而当它出现在值之后时,它就是后缀的(例如 b!)。 44 | 45 | 要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 func 关键字之前指定 prefix 或者 postfix 修饰符: 46 | 47 | extension Vector2D { 48 | static prefix func - (vector: Vector2D) -> Vector2D { 49 | return Vector2D(x: -vector.x, y: -vector.y) 50 | } 51 | } 52 | 53 | let positive = Vector2D(x: 3.0, y: 4.0) 54 | let negative = -positive 55 | // negative 是一个值为 (-3.0, -4.0) 的 Vector2D 实例 56 | let alsoPositive = -negative 57 | // alsoPositive 是一个值为 (3.0, 4.0) 的 Vector2D 实例 58 | 59 | */ 60 | 61 | //: ## 复合赋值运算符 62 | /*: 63 | 复合赋值运算符将赋值运算符(=)与其它运算符进行结合。例如,将加法与赋值结合成加法赋值运算符(+=)。在实现的时候,需要把运算符的左参数设置成 inout 类型,因为这个参数的值会在运算符函数内直接被修改。 64 | 65 | extension Vector2D { 66 | static func += (left: inout Vector2D, right: Vector2D) { 67 | left = left + right 68 | } 69 | } 70 | 71 | var original = Vector2D(x: 1.0, y: 2.0) 72 | let vectorToAdd = Vector2D(x: 3.0, y: 4.0) 73 | original += vectorToAdd 74 | // original 的值现在为 (4.0, 6.0) 75 | 76 | */ 77 | 78 | //: ## 等价运算符 79 | /*: 80 | 自定义的类和结构体没有对等价运算符进行默认实现,等价运算符通常被称为“相等”运算符(==)与“不等”运算符(!=)。对于自定义类型,Swift 无法判断其是否“相等”,因为“相等”的含义取决于这些自定义类型在你的代码中所扮演的角色。 81 | 82 | 为了使用等价运算符能对自定义的类型进行判等运算,需要为其提供自定义实现,实现的方法与其它中缀运算符一样, 并且增加对标准库 Equatable 协议的遵循: 83 | 84 | extension Vector2D: Equatable { 85 | static func == (left: Vector2D, right: Vector2D) -> Bool { 86 | return (left.x == right.x) && (left.y == right.y) 87 | } 88 | } 89 | 90 | let twoThree = Vector2D(x: 2.0, y: 3.0) 91 | let anotherTwoThree = Vector2D(x: 2.0, y: 3.0) 92 | if twoThree == anotherTwoThree { 93 | print("These two vectors are equivalent.") 94 | } 95 | // 打印 “These two vectors are equivalent.” 96 | 97 | Swift 为以下自定义类型等价运算符提供合成实现: 98 | - 只拥有遵循 Equatable 协议存储属性的结构体; 99 | - 只拥有遵循 Equatable 协议关联类型的枚举; 100 | - 没有关联类型的枚举。 101 | 102 | 在类型原本的声明中声明遵循 Equatable 来接收这些默认实现。 103 | 104 | struct Vector3D: Equatable { 105 | var x = 0.0, y = 0.0, z = 0.0 106 | } 107 | 108 | let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0) 109 | let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0) 110 | if twoThreeFour == anotherTwoThreeFour { 111 | print("These two vectors are also equivalent.") 112 | } 113 | // Prints "These two vectors are also equivalent." 114 | 115 | */ 116 | 117 | //: ## 自定义运算符 118 | /*: 119 | 除了实现标准运算符,在 Swift 中还可以声明和实现自定义运算符。 120 | 121 | 新的运算符要使用 operator 关键字在全局作用域内进行定义,同时还要指定 prefix、infix 或者 postfix 修饰符: 122 | 123 | prefix operator +++ 124 | 125 | extension Vector2D { 126 | static prefix func +++ (vector: inout Vector2D) -> Vector2D { 127 | vector += vector 128 | return vector 129 | } 130 | } 131 | 132 | var toBeDoubled = Vector2D(x: 1.0, y: 4.0) 133 | let afterDoubling = +++toBeDoubled 134 | // toBeDoubled 现在的值为 (2.0, 8.0) 135 | // afterDoubling 现在的值也为 (2.0, 8.0) 136 | 137 | */ 138 | 139 | //: ## 自定义中缀运算符的优先级 140 | /*: 141 | 每个自定义中缀运算符都属于某个优先级组。这个优先级组指定了这个运算符和其他中缀运算符的优先级和结合性。没有明确放入优先级组的自定义中缀运算符会放到一个默认的优先级组内,其优先级高于三元运算符。 142 | 143 | 定义了一个新的自定义中缀运算符 +-,此运算符属于 AdditionPrecedence 优先组: 144 | 145 | infix operator +-: AdditionPrecedence 146 | extension Vector2D { 147 | static func +- (left: Vector2D, right: Vector2D) -> Vector2D { 148 | return Vector2D(x: left.x + right.x, y: left.y - right.y) 149 | } 150 | } 151 | 152 | let firstVector = Vector2D(x: 1.0, y: 2.0) 153 | let secondVector = Vector2D(x: 3.0, y: 4.0) 154 | let plusMinusVector = firstVector +- secondVector 155 | // plusMinusVector 是一个 Vector2D 实例,并且它的值为 (4.0, -2.0) 156 | 157 | */ 158 | //: [下一页](@next) 159 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Automatic Reference Counting.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Automatic-Reference-Counting.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 自动引用计数 10 | /*: 11 | Swift 使用自动引用计数(ARC)机制来跟踪和管理应用程序的内存。通常情况下,Swift 内存管理机制会一直起作用,无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。 12 | 13 | - Note: 14 | 引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。 15 | */ 16 | 17 | //: ## 自动引用计数的机制 18 | /*: 19 | 当每次创建一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。 20 | 21 | 当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。 22 | 23 | 当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。如果你试图访问这个实例,你的应用程序很可能会崩溃。 24 | 25 | 为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC 都不会销毁这个实例。 26 | 27 | 为了使上述成为可能,无论将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。 28 | */ 29 | 30 | //: ## 类实例之间的循环强引用 31 | /*: 32 | 在实际情况中,我们可能会写出一个类实例的强引用数永远不能变成 0 的代码。如果两个类实例互相持有对方的强引用,每个实例都让对方一直存在,这种情况就是所谓的循环强引用。 33 | 34 | 可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题。 35 | */ 36 | 37 | //: ## 解决实例之间的循环强引用 38 | /*: 39 | Swift 提供了两种办法用来解决在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。 40 | 41 | 弱引用和无主引用允许循环引用中的一个实例引用而另外一个实例不保持强引用。这样实例能够互相引用而不产生循环强引用。 42 | 43 | 当其他的实例有更短的生命周期时,使用弱引用;,当其他实例有相同的或者更长生命周期时,请使用无主引用。 44 | */ 45 | //: ### 弱引用 46 | /*: 47 | 弱引用不会对其引用的实例保持强引用,不会阻止 ARC 销毁被引用的实例。这个特性防止了引用变为循环强引用。声明属性或者变量时,在前面加上 weak 关键字表明这是一个弱引用。 48 | 49 | 弱引用不会保持所引用的实例,即使引用存在,实例也有可能被销毁。因此,ARC 会在引用的实例被销毁后自动将其赋值为 nil。并且因为弱引用可以允许它们的值在运行时被赋值为 nil,所以它们会被定义为可选类型变量,而不是常量。 50 | 51 | 可以像其他可选值一样,检查弱引用的值是否存在,你将永远不会访问已销毁的实例的引用。 52 | 53 | - Note: 54 | 当 ARC 设置弱引用为 nil 时,属性观察不会被触发。 55 | 56 | 示例: 57 | 58 | class Person { 59 | let name: String 60 | init(name: String) { self.name = name } 61 | var apartment: Apartment? 62 | deinit { print("\(name) is being deinitialized") } 63 | } 64 | 65 | class Apartment { 66 | let unit: String 67 | init(unit: String) { self.unit = unit } 68 | weak var tenant: Person? 69 | deinit { print("Apartment \(unit) is being deinitialized") } 70 | } 71 | 72 | var john: Person? 73 | var unit4A: Apartment? 74 | 75 | john = Person(name: "John Appleseed") 76 | unit4A = Apartment(unit: "4A") 77 | 78 | john!.apartment = unit4A 79 | unit4A!.tenant = john 80 | 81 | john = nil 82 | // 打印 "John Appleseed is being deinitialized" 83 | 84 | unit4A = nil 85 | // 打印 "Apartment 4A is being deinitialized" 86 | */ 87 | 88 | //: ## 无主引用 89 | /*: 90 | 和弱引用类似,无主引用不会牢牢保持住引用的实例。不同的是,无主引用在其他实例有相同或者更长的生命周期时使用。可以在声明属性或者变量时,在前面加上关键字 unowned 表示这是一个无主引用。 91 | 92 | 无主引用通常都被期望拥有值。不过 ARC 无法在实例被销毁后将无主引用设为 nil,因为非可选类型的变量不允许被赋值为 nil。 93 | 94 | - Note: 95 | 使用无主引用,你必须确保引用始终指向一个未销毁的实例。 96 | 如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。 97 | 98 | 示例: 99 | 100 | class Customer { 101 | let name: String 102 | var card: CreditCard? 103 | init(name: String) { 104 | self.name = name 105 | } 106 | deinit { print("\(name) is being deinitialized") } 107 | } 108 | 109 | class CreditCard { 110 | let number: UInt64 111 | unowned let customer: Customer 112 | init(number: UInt64, customer: Customer) { 113 | self.number = number 114 | self.customer = customer 115 | } 116 | deinit { print("Card #\(number) is being deinitialized") } 117 | } 118 | 119 | var john: Customer? 120 | john = Customer(name: "John Appleseed") 121 | john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!) 122 | 123 | john = nil 124 | // 打印 "John Appleseed is being deinitialized" 125 | // 打印 "Card #1234567890123456 is being deinitialized" 126 | 127 | - Note: 128 | 上面的例子展示了如何使用安全的无主引用。对于需要禁用运行时的安全检查的情况(例如,出于性能方面的原因),Swift 还提供了不安全的无主引用。与所有不安全的操作一样,需要负责检查代码以确保其安全性。 可以通过 unowned(unsafe) 来声明不安全无主引用。如果试图在实例被销毁后,访问该实例的不安全无主引用,你的程序会尝试访问该实例之前所在的内存地址,这是一个不安全的操作。 129 | */ 130 | 131 | //: ## 闭包的循环引用 132 | /*: 133 | 循环强引用还会发生在当将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如 self.someProperty,或者闭包中调用了实例的某个方法,例如 self.someMethod()。这两种情况都导致了闭包“捕获”self,从而产生了循环强引用。 134 | 135 | 在闭包中,Swift 提供了一种优雅的方法来解决这个问题,称之为 闭包捕获列表(closure capture list)。 136 | */ 137 | 138 | //: ## 解决闭包的循环强引用 139 | /*: 140 | 在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。 141 | */ 142 | //: ### 定义捕获列表 143 | /*: 144 | 捕获列表中的每一项都由一对元素组成,一个元素是 weak 或 unowned 关键字,另一个元素是类实例的引用(例如 self)或初始化过的变量(如 delegate = self.delegate!)。这些项在方括号中用逗号分开。如果闭包有参数列表和返回类型,把捕获列表放在它们前面: 145 | 146 | lazy var someClosure: (Int, String) -> String = { 147 | [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in 148 | // 这里是闭包的函数体 149 | } 150 | 151 | 如果闭包没有指明参数列表或者返回类型,它们会通过上下文推断,那么可以把捕获列表和关键字 in 放在闭包最开始的地方: 152 | 153 | lazy var someClosure: Void -> String = { 154 | [unowned self, weak delegate = self.delegate!] in 155 | // 这里是闭包的函数体 156 | } 157 | 158 | */ 159 | 160 | //: ### 弱引用和无主引用 161 | /*: 162 | 在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为 无主引用。 163 | 164 | 在被捕获的引用可能会变为 nil 时,将闭包内的捕获定义为 弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为 nil。这使我们可以在闭包体内检查它们是否存在。 165 | 166 | - Note: 167 | 如果被捕获的引用绝对不会变为 nil,应该用无主引用,而不是弱引用。 168 | */ 169 | //: [下一页](@next) 170 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Closures.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Closures.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | 10 | //: # 闭包 11 | /*: 12 | 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的 lambdas 表达式比较相似。 13 | 14 | 闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为**捕获**常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。 15 | 16 | 在[函数](Functions)一节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包的三种形式: 17 | - 全局函数是一个有名字但不会捕获任何值的闭包 18 | - 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包 19 | - 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包 20 | 21 | Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下: 22 | 23 | - 利用上下文推断参数和返回值类型 24 | - 隐式返回单表达式闭包,即单表达式闭包可以省略 return 关键字 25 | - 参数名称缩写 26 | - 尾随闭包语法 27 | */ 28 | 29 | //: ## 闭包表达式 30 | /*: 31 | 闭包表达式是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。 32 | 33 | ### 闭包表达式语法 34 | 闭包表达式语法有如下的一般形式: 35 | 36 | { (parameters) -> return type in 37 | statements 38 | } 39 | 40 | 闭包的参数、返回值的类型与函数的一致。闭包的函数体部分由关键字 in 引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。 41 | 42 | 定义一个排序函数: 43 | 44 | func backward(_ s1: String, _ s2: String) -> Bool { 45 | return s1 > s2 46 | } 47 | var reversedNames = names.sorted(by: backward) 48 | // reversedNames 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"] 49 | 50 | 使用闭包的形式: 51 | 52 | reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in 53 | return s1 > s2 54 | }) 55 | 56 | 简写在一行: 57 | 58 | reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } ) 59 | 60 | ### 根据上下文推断类型 61 | Swift 中闭包可以根据调用的上下文对闭包的参数以及返回值的类型进行类型*推断*,上述的闭包可以简写为: 62 | 63 | reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } ) 64 | 65 | ### 单表达式闭包隐式返回 66 | Swift 中单行闭包表达式可以隐藏返回关键词 `return`: 67 | 68 | reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } ) 69 | 70 | ### 参数名称缩写 71 | Swift 自动为内联闭包提供了参数名称缩写功能,可以直接通过 $0,$1,$2 来顺序调用闭包的参数,以此类推: 72 | 73 | reversedNames = names.sorted(by: { $0 > $1 } ) 74 | 75 | ### 运算符方法 76 | Swift 的 String 类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。而这正好与 sorted(by:) 方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift 可以自动推断出你想使用大于号的字符串函数实现: 77 | 78 | reversedNames = names.sorted(by: >) 79 | 80 | */ 81 | 82 | //: ## 尾随闭包 83 | /*: 84 | 如果需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用*尾随闭包*来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签: 85 | 86 | func someFunctionThatTakesAClosure(closure: () -> Void) { 87 | // 函数体部分 88 | } 89 | 90 | // 以下是不使用尾随闭包进行函数调用 91 | someFunctionThatTakesAClosure(closure: { 92 | // 闭包主体部分 93 | }) 94 | 95 | // 以下是使用尾随闭包进行函数调用 96 | someFunctionThatTakesAClosure() { 97 | // 闭包主体部分 98 | } 99 | 100 | 上述闭包可以改写为: 101 | 102 | reversedNames = names.sorted() { $0 > $1 } 103 | 104 | 如果闭包表达式是函数或方法的唯一参数,当你使用尾随闭包时,甚至可以把 () 省略掉: 105 | 106 | reversedNames = names.sorted { $0 > $1 } 107 | 108 | */ 109 | 110 | //: ## 值捕获 111 | /*: 112 | 闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。 113 | 114 | - Note: 115 | 为了优化,如果一个值不会被闭包改变,或者在闭包创建后不会改变,Swift 可能会改为捕获并保存一份对值的拷贝。 116 | Swift 也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量。 117 | */ 118 | 119 | //: ## 逃逸闭包 120 | /*: 121 | 当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,就称该闭包从函数中逃逸。定义接受闭包作为参数的函数时,通过在参数名之前标注 `@escaping`,用来指明这个闭包是允许“逃逸”出这个函数的: 122 | 123 | var completionHandlers: [() -> Void] = [] 124 | func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { 125 | completionHandlers.append(completionHandler) 126 | } 127 | 128 | 函数参数引用逃逸闭包时必须显式标记 `@escaping` 否则会触发编译器报错。 129 | 130 | 逃逸闭包必须在闭包内部显式引用 `self` ,非逃逸闭包则可以隐式的引用 `self` : 131 | 132 | func someFunctionWithNonescapingClosure(closure: () -> Void) { 133 | closure() 134 | } 135 | 136 | class SomeClass { 137 | var x = 10 138 | func doSomething() { 139 | someFunctionWithEscapingClosure { self.x = 100 } 140 | someFunctionWithNonescapingClosure { x = 200 } 141 | } 142 | } 143 | 144 | let instance = SomeClass() 145 | instance.doSomething() 146 | print(instance.x) 147 | // 打印出 "200" 148 | 149 | completionHandlers.first?() 150 | print(instance.x) 151 | // 打印出 "100" 152 | 153 | */ 154 | 155 | //: ## 自动闭包 156 | /*: 157 | 自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式;自动闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值;自动便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。 158 | 159 | 例如,`assert(condition:message:file:line:)` 函数接受自动闭包作为它的 condition 参数和 message 参数;它的 condition 参数仅会在 debug 模式下被求值,它的 message 参数仅当 condition 参数为 false 时被计算求值。 160 | 161 | 自动闭包能够延迟求值,直到调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。下面的代码展示了闭包如何延时求值: 162 | 163 | var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] 164 | print(customersInLine.count) 165 | // 打印出 "5" 166 | 167 | let customerProvider = { customersInLine.remove(at: 0) } 168 | print(customersInLine.count) 169 | // 打印出 "5" 170 | 171 | print("Now serving \(customerProvider())!") 172 | // Prints "Now serving Chris!" 173 | print(customersInLine.count) 174 | // 打印出 "4" 175 | 176 | 将闭包作为参数传递给函数时,同样能获得延时求值的行为: 177 | 178 | // customersInLine is ["Alex", "Ewa", "Barry", "Daniella"] 179 | func serve(customer customerProvider: () -> String) { 180 | print("Now serving \(customerProvider())!") 181 | } 182 | serve(customer: { customersInLine.remove(at: 0) } ) 183 | // 打印出 "Now serving Alex!" 184 | 185 | - Note: 186 | 过度使用 autoclosures 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。 187 | */ 188 | //: [下一页](@next) 189 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Memory Safety.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Memory-Safety.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 内存安全 10 | /*: 11 | 默认情况下,Swift 会阻止代码里不安全的行为。例如,Swift 会保证变量在使用之前就完成初始化,在内存被回收之后就无法被访问,并且数组的索引会做越界检查。 12 | 13 | Swift 也保证同时访问同一块内存时不会冲突,通过约束代码里对于存储地址的写操作,去获取那一块内存的访问独占权。因为 Swift 自动管理内存,所以大部分时候我们完全不需要考虑内存访问的事情。然而,理解潜在的冲突也是很重要的,可以避免写出访问冲突的代码。而如果你的代码确实存在冲突,那在编译时或者运行时就会得到错误。 14 | */ 15 | //: ## 内存访问冲突 16 | /*: 17 | 内存的访问,会发生在你给变量赋值,或者传递参数给函数时: 18 | 19 | // 向 one 所在的内存区域发起一次写操作 20 | var one = 1 21 | 22 | // 向 one 所在的内存区域发起一次读操作 23 | print("We're number \(one)!") 24 | 25 | 内存访问的冲突会发生在代码尝试同时访问同一个存储地址的时侯。同一个存储地址的多个访问同时发生会造成不可预计或不一致的行为。在 Swift 里,有很多修改值的行为都会持续好几行代码,在修改值的过程中进行访问是有可能发生的。 26 | 27 | - Note: 28 | 这里访问冲突的讨论是在单线程的情境下讨论的,并没有使用并发或者多线程。 29 | 如果你曾经在单线程代码里有访问冲突,Swift 可以保证你在编译或者运行时会得到错误。对于多线程的代码,可以使用 Thread Sanitizer 去帮助检测多线程的冲突。 30 | */ 31 | //: ## 内存访问的典型状况 32 | /*: 33 | 内存访问冲突有三种典型的状况:访问是读还是写,访问的时长,以及被访问的存储地址。特别是,当有两个访问符合下列的情况: 34 | - 至少有一个是写访问 35 | - 它们访问的是同一个存储地址 36 | - 它们的访问在时间线上部分重叠 37 | 38 | 读和写访问的区别很明显:一个写访问会改变存储地址,而读操作不会。存储地址会指向真正访问的位置 —— 例如,一个变量,常量或者属性。内存访问的时长要么是瞬时的,要么是长期的。 39 | 40 | 如果一个访问不可能在其访问期间被其它代码访问,那么就是一个瞬时访问。基于这个特性,两个瞬时访问是不可能同时发生。大多数内存访问都是瞬时的。例如,下面列举的所有读和写访问都是瞬时的: 41 | 42 | func oneMore(than number: Int) -> Int { 43 | return number + 1 44 | } 45 | 46 | var myNumber = 1 47 | myNumber = oneMore(than: myNumber) 48 | print(myNumber) 49 | // 打印 "2" 50 | 51 | 然而,有几种被称为长期访问的内存访问方式,会在别的代码执行时持续进行。瞬时访问和长期访问的区别在于别的代码有没有可能在访问期间同时访问,也就是在时间线上的重叠。一个长期访问可以被别的长期访问或瞬时访问重叠。 52 | 53 | 重叠的访问主要出现在使用 in-out 参数的函数和方法或者结构体的 mutating 方法里。Swift 代码里典型的长期访问会在后面进行讨论。 54 | */ 55 | //: ## In-Out 参数的访问冲突 56 | /*: 57 | 一个函数会对它所有的 in-out 参数进行长期写访问。in-out 参数的写访问会在所有非 in-out 参数处理完之后开始,直到函数执行完毕为止。如果有多个 in-out 参数,则写访问开始的顺序与参数的顺序一致。 58 | 59 | 长期访问的存在会造成一个结果,你不能在原变量以 in-out 形式传入后访问原变量,即使作用域原则和访问权限允许 —— 任何访问原变量的行为都会造成冲突。例如: 60 | 61 | var stepSize = 1 62 | 63 | func increment(_ number: inout Int) { 64 | number += stepSize 65 | } 66 | 67 | increment(&stepSize) 68 | // 错误:stepSize 访问冲突 69 | 70 | 解决这个冲突的一种方式,是复制一份 stepSize 的副本: 71 | 72 | // 复制一份副本 73 | var copyOfStepSize = stepSize 74 | increment(©OfStepSize) 75 | 76 | // 更新原来的值 77 | stepSize = copyOfStepSize 78 | // stepSize 现在的值是 2 79 | 80 | 长期写访问的存在还会造成另一种结果,往同一个函数的多个 in-out 参数里传入同一个变量也会产生冲突,例如: 81 | 82 | func balance(_ x: inout Int, _ y: inout Int) { 83 | let sum = x + y 84 | x = sum / 2 85 | y = sum - x 86 | } 87 | var playerOneScore = 42 88 | var playerTwoScore = 30 89 | balance(&playerOneScore, &playerTwoScore) // 正常 90 | balance(&playerOneScore, &playerOneScore) 91 | // 错误:playerOneScore 访问冲突 92 | 93 | 将 playerOneScore 和 playerTwoScore 作为参数传入不会产生错误 —— 有两个访问重叠了,但它们访问的是不同的内存位置。相反,将 playerOneScore 作为参数同时传入就会产生冲突,因为它会发起两个写访问,同时访问同一个的存储地址。 94 | 95 | - Note: 96 | 因为操作符也是函数,它们也会对 in-out 参数进行长期访问。 97 | */ 98 | //: ### 方法里 self 的访问冲突 99 | /*: 100 | 一个结构体的 mutating 方法会在调用期间对 self 进行写访问。例如,想象一下这么一个游戏,每一个玩家都有血量,受攻击时血量会下降,并且有敌人的数量,使用特殊技能时会减少敌人数量。 101 | 102 | struct Player { 103 | var name: String 104 | var health: Int 105 | var energy: Int 106 | 107 | static let maxHealth = 10 108 | mutating func restoreHealth() { 109 | health = Player.maxHealth 110 | } 111 | } 112 | 113 | 在上面的 restoreHealth() 方法里,一个对于 self 的写访问会从方法开始直到方法 return。在这种情况下,restoreHealth() 里的其它代码不可以对 Player 实例的属性发起重叠的访问。下面的 shareHealth(with:) 方法接受另一个 Player 的实例作为 in-out 参数,产生了访问重叠的可能性。 114 | 115 | extension Player { 116 | mutating func shareHealth(with teammate: inout Player) { 117 | balance(&teammate.health, &health) 118 | } 119 | } 120 | 121 | var oscar = Player(name: "Oscar", health: 10, energy: 10) 122 | var maria = Player(name: "Maria", health: 5, energy: 10) 123 | oscar.shareHealth(with: &maria) // 正常 124 | 125 | 调用 shareHealth(with:) 方法去把 oscar 玩家的血量分享给 maria 玩家并不会造成冲突。在方法调用期间会对 oscar 发起写访问,因为在 mutating 方法里 self 就是 oscar,同时对于 maria 也会发起写访问,因为 maria 作为 in-out 参数传入。过程如下,它们会访问内存的不同位置。即使两个写访问重叠了,它们也不会冲突。 126 | 127 | 如果你将 oscar 作为参数传入 shareHealth(with:) 里,就会产生冲突: 128 | 129 | oscar.shareHealth(with: &oscar) 130 | // 错误:oscar 访问冲突 131 | 132 | mutating 方法在调用期间需要对 self 发起写访问,而同时 in-out 参数也需要写访问。在方法里,self 和 teammate 都指向了同一个存储地址,对于同一块内存同时进行两个写访问,并且它们重叠了,就此产生了冲突。 133 | */ 134 | //: ### 属性的访问冲突 135 | /*: 136 | 如结构体,元组和枚举的类型都是由多个独立的值组成的,例如结构体的属性或元组的元素。因为它们都是值类型,修改值的任何一部分都是对于整个值的修改,意味着其中一个属性的读或写访问都需要访问整一个值。例如,元组元素的写访问重叠会产生冲突: 137 | 138 | var playerInformation = (health: 10, energy: 20) 139 | balance(&playerInformation.health, &playerInformation.energy) 140 | // 错误:playerInformation 的属性访问冲突 141 | 142 | 下面的代码展示了一样的错误,对于一个存储在全局变量里的结构体属性的写访问重叠了。 143 | 144 | var holly = Player(name: "Holly", health: 10, energy: 10) 145 | balance(&holly.health, &holly.energy) // 错误 146 | 147 | 大多数对于结构体属性的访问都会安全的重叠。例如,将上面例子里的变量 holly 改为本地变量而非全局变量,编译器就会可以保证这个重叠访问时安全的: 148 | 149 | func someFunction() { 150 | var oscar = Player(name: "Oscar", health: 10, energy: 10) 151 | balance(&oscar.health, &oscar.energy) // 正常 152 | } 153 | 154 | oscar 的 health 和 energy 都作为 in-out 参数传入了 balance(_:_:) 里。编译器可以保证内存安全,因为两个存储属性任何情况下都不会相互影响。 155 | */ 156 | /*: 157 | 限制结构体属性的重叠访问对于内存安全并不总是必要的。内存安全很有必要,但访问独占权的要求比内存安全还要更严格 —— 意味着即使有些代码违反了访问独占权的原则,也是内存安全的。如果编译器可以保证这种非专属的访问是安全的,那 Swift 就会允许这种内存安全的行为。特别是当遵循下面的原则时,它可以保证结构体属性的重叠访问是安全的: 158 | 159 | - 你访问的是实例的存储属性,而不是计算属性或类的属性 160 | - 结构体是本地变量的值,而非全局变量 161 | - 结构体要么没有被闭包捕获,要么只被非逃逸闭包捕获了 162 | 163 | 如果编译器无法保证访问的安全性,它就不会允许访问。 164 | */ 165 | //: [下一页](@next) 166 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Error Handling.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error-Handling.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 错误处理 10 | /*: 11 | 错误处理(Error handling)是响应错误以及从错误中恢复的过程。Swift 提供了在运行时对可恢复错误的抛出、捕获、传递和操作的一等公民支持。 12 | */ 13 | 14 | //: ## 表示并抛出错误 15 | /*: 16 | 在 Swift 中,错误用符合 Error 协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。 17 | 18 | Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息: 19 | 20 | enum VendingMachineError: Error { 21 | case invalidSelection //选择无效 22 | case insufficientFunds(coinsNeeded: Int) //金额不足 23 | case outOfStock //缺货 24 | } 25 | 26 | 抛出错误使用 throw 关键字: 27 | 28 | throw VendingMachineError.insufficientFunds(coinsNeeded: 5) 29 | 30 | */ 31 | 32 | //: ## 处理错误 33 | /*: 34 | Swift 中有 4 种处理错误的方式。可以把函数抛出的错误传递给调用此函数的代码、用 do-catch 语句处理错误、将错误作为可选类型处理、或者断言此错误根本不会发生。在调用一个能抛出错误的函数、方法或者构造器之前,加上 try 关键字,或者 try? 或 try! 这种变体。 35 | 36 | Swift 中的错误处理和其他语言中用 try,catch 和 throw 进行异常处理很像。和其他语言中(包括 Objective-C )的异常处理不同的是,Swift 中的错误处理并不涉及解除调用栈,这是一个计算代价高昂的过程。就此而言,throw 语句的性能特性是可以和 return 语句相媲美的。 37 | 38 | */ 39 | 40 | //: ## 用 throwing 函数传递错误 41 | /*: 42 | 表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上 throws 关键字即可。一个标有 throws 关键字的函数被称作throwing 函数。如果这个函数指明了返回值类型,throws 关键词需要写在箭头(->)的前面。 43 | 44 | func canThrowErrors() throws -> String 45 | func cannotThrowErrors() -> String 46 | 47 | 一个 throwing 函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域。 48 | 49 | 只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。 50 | 51 | struct Item { 52 | var price: Int 53 | var count: Int 54 | } 55 | 56 | class VendingMachine { 57 | var inventory = [ 58 | "Candy Bar": Item(price: 12, count: 7), 59 | "Chips": Item(price: 10, count: 4), 60 | "Pretzels": Item(price: 7, count: 11) 61 | ] 62 | var coinsDeposited = 0 63 | func dispenseSnack(snack: String) { 64 | print("Dispensing \(snack)") 65 | } 66 | 67 | func vend(itemNamed name: String) throws { 68 | guard let item = inventory[name] else { 69 | throw VendingMachineError.invalidSelection 70 | } 71 | 72 | guard item.count > 0 else { 73 | throw VendingMachineError.outOfStock 74 | } 75 | 76 | guard item.price <= coinsDeposited else { 77 | throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited) 78 | } 79 | 80 | coinsDeposited -= item.price 81 | 82 | var newItem = item 83 | newItem.count -= 1 84 | inventory[name] = newItem 85 | 86 | print("Dispensing \(name)") 87 | } 88 | } 89 | 90 | let favoriteSnacks = [ 91 | "Alice": "Chips", 92 | "Bob": "Licorice", 93 | "Eve": "Pretzels", 94 | ] 95 | func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws { 96 | let snackName = favoriteSnacks[person] ?? "Candy Bar" 97 | try vendingMachine.vend(itemNamed: snackName) 98 | } 99 | 100 | throwing 构造器能像 throwing 函数一样传递错误: 101 | 102 | struct PurchasedSnack { 103 | let name: String 104 | init(name: String, vendingMachine: VendingMachine) throws { 105 | try vendingMachine.vend(itemNamed: name) 106 | self.name = name 107 | } 108 | } 109 | 110 | */ 111 | 112 | //: ## 用 Do-Catch 处理错误 113 | /*: 114 | 可以使用一个 do-catch 语句运行一段闭包代码来处理错误。如果在 do 子句中的代码抛出了一个错误,这个错误会与 catch 子句做匹配: 115 | 116 | do { 117 | try expression 118 | statements 119 | } catch pattern 1 { 120 | statements 121 | } catch pattern 2 where condition { 122 | statements 123 | } 124 | 125 | 在 catch 后面写一个匹配模式来匹配错误类型。如果一条 catch 子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为 error 的局部常量。catch 子句不必将 do 子句中的代码所抛出的每一个可能的错误都作处理。 126 | 127 | var vendingMachine = VendingMachine() 128 | vendingMachine.coinsDeposited = 8 129 | do { 130 | try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine) 131 | } catch VendingMachineError.invalidSelection { 132 | print("Invalid Selection.") 133 | } catch VendingMachineError.outOfStock { 134 | print("Out of Stock.") 135 | } catch VendingMachineError.insufficientFunds(let coinsNeeded) { 136 | print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.") 137 | } 138 | // 打印 “Insufficient funds. Please insert an additional 2 coins.” 139 | 140 | */ 141 | 142 | //: ## 将错误转换成可选值 143 | /*: 144 | 可以使用 try? 通过将错误转换成一个可选值来处理错误。如果在执行 try? 表达式时一个错误被抛出,那么表达式的值就是 nil: 145 | 146 | func someThrowingFunction() throws -> Int { 147 | // ... 148 | } 149 | 150 | let x = try? someThrowingFunction() 151 | 152 | let y: Int? 153 | do { 154 | y = try someThrowingFunction() 155 | } catch { 156 | y = nil 157 | } 158 | 159 | */ 160 | 161 | //: ## 强制解包错误 162 | /*: 163 | 当某个 throwing 函数实际上在运行时是不会抛出错误时,可以在表达式前面写 try! 来进行强制解包错误,这会把调用包装在一个不会有错误抛出的运行时断言中。如果真的抛出了错误,你会得到一个运行时错误。 164 | 165 | let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg" 166 | 167 | */ 168 | 169 | //: ## 函数退出WatchDog 170 | /*: 171 | 可以使用 defer 语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,或是由于诸如 return、break 的语句。例如,你可以用 defer 语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。 172 | 173 | defer 语句将代码的执行延迟到当前的作用域退出之前,延迟执行的操作会按照它们声明的顺序从后往前执行: 174 | 175 | func processFile(filename: String) throws { 176 | if exists(filename) { 177 | let file = open(filename) 178 | defer { 179 | close(file) 180 | } 181 | while let line = try file.readline() { 182 | // 处理文件。 183 | } 184 | // close(file) 会在这里被调用,即作用域的最后。 185 | } 186 | } 187 | 188 | */ 189 | //: [下一页](@next) 190 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Enumerations.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Enumerations.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 枚举 10 | /*: 11 | 在 Swift 中,枚举类型是一等(first-class)类型;它采用了很多在传统上只被类(class)所支持的特性,例如计算属性(computed properties),用于提供枚举值的附加信息,实例方法(instance methods),用于提供和枚举值相关联的功能。枚举也可以定义构造函数(initializers)来提供一个初始值;可以在原始实现的基础上扩展它们的功能;还可以遵循协议(protocols)来提供标准的功能。 12 | 13 | 和 C 语言的枚举相比,Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为“原始”值),则该值的类型可以是字符串、字符,或是一个整型值或浮点数。 14 | 15 | 此外,枚举成员可以指定任意类型的关联值存储到枚举成员中,就像其他语言中的联合体(unions)和变体(variants)。你可以在一个枚举中定义一组相关的枚举成员,每一个枚举成员都可以有适当类型的关联值。 16 | */ 17 | 18 | //: ## 枚举语法 19 | /*: 20 | 使用 enum 关键词来创建枚举并且把它们的整个定义放在一对大括号内: 21 | 22 | enum SomeEnumeration { 23 | // 枚举定义放在这里 24 | } 25 | 26 | enum CompassPoint { 27 | case north 28 | case south 29 | case east 30 | case west 31 | } 32 | 33 | var directionToHead = CompassPoint.west 34 | 35 | enum Planet { 36 | case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune 37 | } 38 | 39 | 枚举类型的变量声明为确定的类型之后,Swift 就可以使用类型推断的方式推断出值的类型: 40 | 41 | directionToHead = .east 42 | 43 | */ 44 | 45 | //: ## 使用 Switch 语句匹配枚举值 46 | /*: 47 | 可以使用 switch 语句匹配单个枚举值: 48 | 49 | directionToHead = .south 50 | switch directionToHead { 51 | case .north: 52 | print("Lots of planets have a north") 53 | case .south: 54 | print("Watch out for penguins") 55 | case .east: 56 | print("Where the sun rises") 57 | case .west: 58 | print("Where the skies are blue") 59 | } 60 | // 打印 "Watch out for penguins” 61 | 62 | */ 63 | 64 | //: ## 关联值 65 | /*: 66 | 可以定义 Swift 枚举来存储任意类型的关联值,每个枚举成员的关联值类型可以各不相同。枚举的这种特性跟其他语言中的可识别联合(discriminated unions),标签联合(tagged unions),或者变体(variants)相似: 67 | 68 | enum Barcode { 69 | case upc(Int, Int, Int, Int) 70 | case qrCode(String) 71 | } 72 | 73 | var productBarcode = Barcode.upc(8, 85909, 51226, 3) 74 | productBarcode = .qrCode("ABCDEFGHIJKLMNOP") 75 | 76 | 关联值可以被提取出来作为 switch 语句的一部分。在 switch 的 case 分支代码中提取每个关联值作为一个常量(用 let 前缀)或者作为一个变量(用 var 前缀)来使用: 77 | 78 | switch productBarcode { 79 | case .upc(let numberSystem, let manufacturer, let product, let check): 80 | print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).") 81 | case .qrCode(let productCode): 82 | print("QR code: \(productCode).") 83 | } 84 | // 打印 "QR code: ABCDEFGHIJKLMNOP." 85 | 86 | 或者: 87 | 88 | switch productBarcode { 89 | case let .upc(numberSystem, manufacturer, product, check): 90 | print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).") 91 | case let .qrCode(productCode): 92 | print("QR code: \(productCode).") 93 | } 94 | // 输出 "QR code: ABCDEFGHIJKLMNOP." 95 | 96 | */ 97 | 98 | //: ## 原始值 99 | /*: 100 | Swift 中,枚举成员可以被默认值(称为原始值)预填充,这些原始值的类型必须相同,声明原始值只需要在枚举定义后加上 `:` `RawValueType` 即可: 101 | 102 | enum ASCIIControlCharacter: Character { 103 | case tab = "\t" 104 | case lineFeed = "\n" 105 | case carriageReturn = "\r" 106 | } 107 | 108 | 原始值可以是字符串、字符,或者任意整型值或浮点型值。原始值不需要显式地为每一个枚举成员设置原始值,Swift 将会自动赋值。 109 | 110 | 声明为 `Int` 原始值的枚举跟 `C` 语言类似,第一个枚举值默认为 `0` ,往后递增,也可以指定某一个 `case` 的值往后递增: 111 | 112 | enum Planet: Int { 113 | case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune 114 | } 115 | 116 | 当使用字符串作为枚举类型的原始值时,每个枚举成员的隐式原始值为该枚举成员的名称: 117 | 118 | enum CompassPoint: String { 119 | case north, south, east, west 120 | } 121 | 122 | 使用枚举成员的 rawValue 属性可以访问该枚举成员的原始值: 123 | 124 | let earthsOrder = Planet.earth.rawValue 125 | // earthsOrder 值为 3 126 | 127 | let sunsetDirection = CompassPoint.west.rawValue 128 | // sunsetDirection 值为 "west" 129 | 130 | */ 131 | 132 | //: ## 使用原始值初始化枚举 133 | /*: 134 | 在定义枚举类型的时候使用原始值,就会自动生成一个初始化方法,这个方法接收一个叫做 rawValue 的参数,参数类型即为原始值类型,返回值则是一个可选的枚举类型: 135 | 136 | let possiblePlanet = Planet(rawValue: 7) 137 | // possiblePlanet 类型为 Planet? 值为 Planet.uranus 138 | 139 | let positionToFind = 11 140 | if let somePlanet = Planet(rawValue: positionToFind) { 141 | switch somePlanet { 142 | case .earth: 143 | print("Mostly harmless") 144 | default: 145 | print("Not a safe place for humans") 146 | } 147 | } else { 148 | print("There isn't a planet at position \(positionToFind)") 149 | } 150 | // 输出 "There isn't a planet at position 11 151 | 152 | */ 153 | 154 | //: ## 递归枚举 155 | /*: 156 | 递归枚举是一种枚举类型,枚举本身可以作为枚举定义的关联值,编译器会插入一个间接层。在枚举成员前加上 indirect 来表示该成员可递归: 157 | 158 | enum ArithmeticExpression { 159 | case number(Int) 160 | indirect case addition(ArithmeticExpression, ArithmeticExpression) 161 | indirect case multiplication(ArithmeticExpression, ArithmeticExpression) 162 | } 163 | 164 | 在枚举类型开头加上 indirect 关键字来表明它的所有成员都是可递归的: 165 | 166 | indirect enum ArithmeticExpression { 167 | case number(Int) 168 | case addition(ArithmeticExpression, ArithmeticExpression) 169 | case multiplication(ArithmeticExpression, ArithmeticExpression) 170 | } 171 | 172 | let five = ArithmeticExpression.number(5) 173 | let four = ArithmeticExpression.number(4) 174 | let sum = ArithmeticExpression.addition(five, four) 175 | let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2)) 176 | 177 | */ 178 | indirect enum ArithmeticExpression { 179 | case number(Int) 180 | case addition(ArithmeticExpression, ArithmeticExpression) 181 | case multiplication(ArithmeticExpression, ArithmeticExpression) 182 | } 183 | 184 | let five = ArithmeticExpression.number(5) 185 | let four = ArithmeticExpression.number(4) 186 | let sum = ArithmeticExpression.addition(five, four) 187 | let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2)) 188 | //: [下一页](@next) 189 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Pattern Matching.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import Foundation 4 | 5 | //: # 模式匹配 6 | /*: 7 | 模式匹配是 Swift 中非常常见的一种编程模式,使用模式匹配,可以帮助我们写出简明、清晰以及易读的代码,使我们的代码变得简洁而强大。 8 | */ 9 | //: ## 条件判断中的模式匹配 10 | /*: 11 | 条件判断是我们使用最普遍的流程控制,在 Swift 中,只能接受 Bool 类型的值作为条件体;除了直接判断 Bool 值之外,我们还能使用使用条件语句进行可选绑定,这在我们开发中是非常常用的方式。 12 | */ 13 | //: ### 匹配枚举值 14 | /*: 15 | 在 Swift 中,创建的枚举类型默认是不可比较的(没有实现`Comparable`协议),这就意味着我们不能直接使用`==`操作符来判断两个枚举值是否相等,这种情况下,需要使用模式匹配: 16 | */ 17 | //: 创建一个枚举类型: 18 | enum Result { 19 | case success 20 | case failure 21 | } 22 | //: 初始化一个枚举值: 23 | let result = Result.success 24 | //: 使用模式匹配来判断创建的枚举值的值: 25 | if case .success = result { 26 | print("Value of result is success.") 27 | } 28 | //: ### 可选绑定 29 | //: 创建一个可选值: 30 | let optionalInt: Int? = 1 31 | //: 使用可选绑定的方式进行解包: 32 | if let val = optionalInt { 33 | print("The value of optionalInt is \(val)") 34 | } 35 | func handleGuard() { 36 | guard let val = optionalInt else { 37 | return 38 | } 39 | 40 | print("The value of optionalInt is \(val)") 41 | } 42 | handleGuard() 43 | //: 可选绑定的另外一种模式,这也是可选绑定中最基础的模式: 44 | if case .some(let val) = optionalInt { 45 | print("The value of optionalInt is \(val)") 46 | } 47 | //: 还可以简化为: 48 | if case let val? = optionalInt { 49 | print("The value of optionalInt is \(val)") 50 | } 51 | //: ## 循环中的模式匹配 52 | //: 问题来了,`if` `let` 模式的可选绑定,只能实现一个可选值的绑定,如果我们需要匹配一个数组里边的可选值怎么办呢?这时候我们就不能使用 `if` `let` 的形式了,需要使用到 `if` `case` `let` 的形式 53 | //: 创建一个包含可选值的数组: 54 | let values: [Int?] = [1, nil, 3, nil, 5, nil, 7, nil, 9, nil] 55 | //: 进行遍历: 56 | for val in values { 57 | print("Value in values is \(String(describing: val))") 58 | } 59 | //: 或者: 60 | var valuesIterator = values.makeIterator() 61 | while let val = valuesIterator.next() { 62 | print("Value in values is \(String(describing: val))") 63 | } 64 | //: 我们得到了所有的值与可选值,如果我们需要过滤可选值,我们可以这样做: 65 | for val in values.compactMap({ $0 }) { 66 | print("Value in values is \(val)") 67 | } 68 | //: 这样做,增加了时间复杂度,需要进行两次遍历才能将数据过滤出来。我们可以使用模式匹配的方式来这样做: 69 | for case let val? in values { 70 | print("Value in values is \(val)") 71 | } 72 | //: 或者: 73 | valuesIterator = values.makeIterator() 74 | while let val = valuesIterator.next(), val != nil { 75 | print("Value in values is \(String(describing: val))") 76 | } 77 | //: 这样就可以将 nil 值给过滤了,是不是很简单?还可以使用 `for` `case` 匹配枚举值数组: 78 | let results: [Result] = [.success, .failure] 79 | for case .success in results { 80 | print("Values in results contains success.") 81 | break 82 | } 83 | //: 对于复杂的枚举类型: 84 | enum NetResource { 85 | case http(resource: String) 86 | case ftp(resource: String) 87 | } 88 | let nets: [NetResource] = [.http(resource: "https://www.baidu.com"), .http(resource: "https://www.apple.cn"), .ftp(resource: "ftp://192.0.0.1")] 89 | //: 过滤 http 的值: 90 | for case .http(let resource) in nets { 91 | print("HTTP resource \(resource)") 92 | } 93 | //: ### `for` 循环使用 `where` 从句 94 | //: 除此之外,我们还可以在 `for` 循环后边跟上一个 `where` 从句来进行模式匹配: 95 | for notNilValue in values where notNilValue != nil { 96 | print("Not nil value: \(String(describing: notNilValue!))") 97 | } 98 | //: 查询一个数组里边所有能被3整除的数: 99 | let rangeValues = Array(0...999) 100 | for threeDivideValue in rangeValues where threeDivideValue % 3 == 0 { 101 | print("Three devide value: \(threeDivideValue)") 102 | } 103 | //: 查询所有含有3的数: 104 | for containsThree in rangeValues where String(containsThree).contains("3") { 105 | print("Value contains three: \(containsThree)") 106 | } 107 | //: ## Switch 中的模式匹配 108 | /*: 109 | Switch 中的模式匹配也很常用,在 Switch 中合理地使用模式匹配可以为我们带来很多好处,可以使我们的代码更简洁,同时可以减少代码量和增加开发效率。 110 | */ 111 | //: ### 区间匹配 112 | let value = 188 113 | switch value { 114 | case 0..<50: 115 | print("The value is in range [0, 50)") 116 | case 50..<100: 117 | print("The value is in range [50, 100)") 118 | case 100..<150: 119 | print("The value is in range [100, 150)") 120 | case 150..<200: 121 | print("The value is in range [150, 200)") 122 | case 200...: 123 | print("The value is in range [200, ") 124 | default: break 125 | } 126 | // The value is in range [150, 200) 127 | //: ### 匹配元组类型 128 | /*: 129 | 创建一个元组类型: 130 | */ 131 | let tuples: (Int, String) = (httpCode: 404, status: "Not Found.") 132 | //: 进行匹配: 133 | switch tuples { 134 | case (400..., let status): 135 | print("The http code is 40x, http status is \(status)") 136 | default: break 137 | } 138 | //: 创建一个点: 139 | let somePoint = (1, 1) 140 | //: 进行匹配: 141 | switch somePoint { 142 | case (0, 0): 143 | print("\(somePoint) is at the origin") 144 | case (_, 0): 145 | print("\(somePoint) is on the x-axis") 146 | case (0, _): 147 | print("\(somePoint) is on the y-axis") 148 | case (-2...2, -2...2): 149 | print("\(somePoint) is inside the box") 150 | default: 151 | print("\(somePoint) is outside of the box") 152 | } 153 | //: 如上,我们在匹配的时候可以使用下划线 `_` 对值进行忽略: 154 | switch tuples { 155 | case (404, _): 156 | print("The http code is 404 not found.") 157 | default: break 158 | } 159 | //: ### 在 `switch` `case` 中使用 `where` 从句 160 | //: 在 case 中使用 where 从句可以使我们的模式匹配看起来更加精简,使匹配的模式更加紧凑: 161 | let yetAnotherPoint = (1, -1) 162 | switch yetAnotherPoint { 163 | case let (x, y) where x == y: 164 | print("(\(x), \(y)) is on the line x == y") 165 | case let (x, y) where x == -y: 166 | print("(\(x), \(y)) is on the line x == -y") 167 | case let (x, y): 168 | print("(\(x), \(y)) is just some arbitrary point") 169 | } 170 | //: # 总结 171 | //: ## Swift 中模式匹配的种类 172 | /*: 173 | 模式匹配可以说是 Swift 中非常强大的一种编程模式,使用良好的模式匹配,可以帮助我们写出简介、优雅的代码,Swift 中的模式匹配包括以下种类: 174 | 175 | - 条件判断:`if`, `guard` 176 | - 可选绑定:`if` `let`, `guard` `let`, `while` `let` ... 177 | - 循环体:`for`, `while`, `repeat` `while` 178 | - `switch` 179 | - `do` `catch` 180 | */ 181 | //: ## 什么时候使用 `where` 从句? 182 | /*: 183 | 我们可以在前文的例子中看到,在很多进行模式匹配的地方还使用了 `where` 从句,`where` 从句的作用就相当于在模式匹配的基础上在加上条件限制,使用 `where` 从句等价于: 184 | */ 185 | for notNilValue in values { 186 | if notNilValue != nil { 187 | print("Not nil value: \(String(describing: notNilValue!))") 188 | } 189 | } 190 | /*: 191 | 可以看出,使用 `where` 从句可以使我们的代码更加简洁和易读,什么时候使用 `where` ? 或者说在哪里可以使用 `where` ? Swift 文档中并没有对 `where` 的详细使用进行介绍,但是在实践中发现,`where` 可以使用在以下地方: 192 | 193 | - `for` 循环语句 194 | - `switch` 分支 195 | 196 | 而对于 `if`, `guard` 与 `while` ,我们不能在其后面添加 `where` 从句,因为他们本身可以进行多个条件的组合. `where` 从句还有一个用法就是对泛型类型进行类型约束,这在泛型的章节中会有介绍. 197 | */ 198 | //: [Next](@next) 199 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Functions.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Functions.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 函数 10 | /*: 11 | `Swift` 统一的函数语法非常的灵活,可以用来表示任何函数,包括从最简单的没有参数名字的 `C` 风格函数,到复杂的带局部和外部参数名的 `Objective-C` 风格函数。参数可以提供默认值,以简化函数调用。参数也传地址引用。 12 | 13 | 在 `Swift` 中,每个函数都有一个由函数的参数值类型和返回值类型组成的类型。我们可以把函数类型当做任何其他普通变量类型一样处理,这样就可以更简单地把函数当做别的函数的参数,也可以从其他函数中返回函数。函数的定义可以写在其他函数定义中,这样可以在嵌套函数范围内实现功能封装。 14 | 15 | 在 `Swift` 中,函数是一等公民。 16 | */ 17 | 18 | //: ## 函数的定义与调用 19 | /*: 20 | 定义一个函数,通过声明参数列表和函数的返回类型即可,语法:`func` `functionName(param1:` `Type`, `param2:` `Type)` `->` `ReturnType`; 21 | 22 | 调用函数时通过点语法调用,传入指定类型的参数即可实现调用: 23 | 24 | func greet(person: String) -> String { 25 | let greeting = "Hello, " + person + "!" 26 | return greeting 27 | } 28 | 29 | print(greet(person: "Anna")) 30 | // 打印 "Hello, Anna!" 31 | print(greet(person: "Brian")) 32 | // 打印 "Hello, Brian!" 33 | 34 | 函数定义的各种形式: 35 | - 无参函数:`func` `functionName()` `->` `ReturnType` 36 | - 无返回值函数:`func` `functionName(param1:` `Type`, `param2:` `Type)` 37 | */ 38 | 39 | //: ## 参数标签和参数名称 40 | /*: 41 | 每个函数参数都有一个参数标签(argument label)以及一个参数名称(parameter name)。参数标签在调用函数的时候使用;调用的时候需要将函数的参数标签写在对应的参数前面。参数名称在函数的实现中使用。默认情况下,函数参数使用参数名称来作为它们的参数标签: 42 | 43 | func someFunction(firstParameterName: Int, secondParameterName: Int) { 44 | // 在函数体内,firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值 45 | } 46 | someFunction(firstParameterName: 1, secondParameterName: 2) 47 | 48 | ### 指定参数标签 49 | 可以在参数名称前指定它的参数标签,中间以空格分隔: 50 | 51 | func someFunction(argumentLabel parameterName: Int) { 52 | // 在函数体内,parameterName 代表参数值 53 | } 54 | 55 | ### 忽略参数标签 56 | 如果你不希望为某个参数添加一个标签,可以使用一个下划线(_)来代替一个明确的参数标签: 57 | 58 | func someFunction(_ firstParameterName: Int, secondParameterName: Int) { 59 | // 在函数体内,firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值 60 | } 61 | someFunction(1, secondParameterName: 2) 62 | 63 | */ 64 | 65 | //: ## 默认参数值 66 | /*: 67 | 可以在函数体中通过给参数赋值来为任意一个参数定义默认值(Deafult Value)。当默认值被定义后,调用这个函数时可以忽略这个参数: 68 | 69 | func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) { 70 | // 如果你在调用时候不传第二个参数,parameterWithDefault 会值为 12 传入到函数体中。 71 | } 72 | someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault = 6 73 | someFunction(parameterWithoutDefault: 4) // parameterWithDefault = 12 74 | 75 | */ 76 | func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) { 77 | } 78 | someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) 79 | someFunction(parameterWithoutDefault: 4) 80 | //: ## 可变参数 81 | /*: 82 | 一个可变参数(variadic parameter)可以接受零个或多个值。函数调用时,可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入(...)的方式来定义可变参数。 83 | 84 | 可变参数的传入值在函数体中变为此类型的一个数组。例如,一个叫做 numbers 的 Double... 型可变参数,在函数体内可以当做一个叫 numbers 的 [Double] 型的数组常量。 85 | 86 | func arithmeticMean(_ numbers: Double...) -> Double { 87 | var total: Double = 0 88 | for number in numbers { 89 | total += number 90 | } 91 | return total / Double(numbers.count) 92 | } 93 | arithmeticMean(1, 2, 3, 4, 5) 94 | // 返回 3.0, 是这 5 个数的平均数。 95 | arithmeticMean(3, 8.25, 18.75) 96 | // 返回 10.0, 是这 3 个数的平均数。 97 | 98 | - Note: 99 | 一个函数只能有一个可变参数! 100 | */ 101 | 102 | //: ## `in-out` 参数 103 | /*: 104 | 函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。如果想要通过指针或者引用的方式读写一个参数,那么就应该把这个参数定义为输入输出参数(In-Out Parameters)。 105 | 106 | 定义一个输入输出参数时,在参数定义前加 `inout` 关键字。 107 | 108 | 调用函数时只能传递变量给 in-out 参数。不能传入常量或者字面量,因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加 & 符,表示这个值可以被函数修改: 109 | 110 | func swapTwoInts(_ a: inout Int, _ b: inout Int) { 111 | let temporaryA = a 112 | a = b 113 | b = temporaryA 114 | } 115 | 116 | var someInt = 3 117 | var anotherInt = 107 118 | swapTwoInts(&someInt, &anotherInt) 119 | print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") 120 | // 打印 "someInt is now 107, and anotherInt is now 3" 121 | 122 | - Note: 123 | 输入输出参数不能有默认值,而且可变参数不能用 inout 标记。 124 | */ 125 | func swapTwoInts(_ a: inout Int, _ b: inout Int) { 126 | let temporaryA = a 127 | a = b 128 | b = temporaryA 129 | } 130 | 131 | var someInt = 3 132 | var anotherInt = 107 133 | swapTwoInts(&someInt, &anotherInt) 134 | print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") 135 | //: ## 函数类型 136 | /*: 137 | 每个函数都有种特定的函数类型,函数的类型由函数的参数类型和返回类型组成:`func` `functionName(param1:` `Int`, `param2:` `Int)` `->` `Int` 函数的类型为:`(Int,` `Int)` `->` `Int`。 138 | 139 | 在 `Swift` 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将适当的函数赋值给它: 140 | 141 | var mathFunction: (Int, Int) -> Int = addTwoInts 142 | 143 | 用 `(Int,` `Int) `->` `Int` 这样的函数类型作为另一个函数的参数类型: 144 | 145 | func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) { 146 | print("Result: \(mathFunction(a, b))") 147 | } 148 | printMathResult(addTwoInts, 3, 5) 149 | // 打印 "Result: 8" 150 | 151 | 还可以用函数类型作为另一个函数的返回类型。需要做的是在返回箭头(->)后写一个完整的函数类型: 152 | 153 | func chooseStepFunction(backward: Bool) -> (Int) -> Int { 154 | return backward ? stepBackward : stepForward 155 | } 156 | 157 | var currentValue = 3 158 | let moveNearerToZero = chooseStepFunction(backward: currentValue > 0) 159 | // moveNearerToZero 现在指向 stepBackward() 函数。 160 | 161 | */ 162 | 163 | //: ## 嵌套函数 164 | /*: 165 | 在 `Swift` 中把函数定义在别的函数体中,称作 嵌套函数(nested functions)。 166 | 167 | 默认情况下,嵌套函数是对外界不可见的,但是可以被它们的外围函数(enclosing function)调用。一个外围函数也可以返回它的某一个嵌套函数,使得这个函数可以在其他域中被使用: 168 | 169 | func chooseStepFunction(backward: Bool) -> (Int) -> Int { 170 | func stepForward(input: Int) -> Int { return input + 1 } 171 | func stepBackward(input: Int) -> Int { return input - 1 } 172 | return backward ? stepBackward : stepForward 173 | } 174 | var currentValue = -4 175 | let moveNearerToZero = chooseStepFunction(backward: currentValue > 0) 176 | // moveNearerToZero now refers to the nested stepForward() function 177 | while currentValue != 0 { 178 | print("\(currentValue)... ") 179 | currentValue = moveNearerToZero(currentValue) 180 | } 181 | print("zero!") 182 | // -4... 183 | // -3... 184 | // -2... 185 | // -1... 186 | // zero! 187 | */ 188 | func chooseStepFunction(backward: Bool) -> (Int) -> Int { 189 | func stepForward(input: Int) -> Int { return input + 1 } 190 | func stepBackward(input: Int) -> Int { return input - 1 } 191 | return backward ? stepBackward : stepForward 192 | } 193 | var currentValue = -4 194 | let moveNearerToZero = chooseStepFunction(backward: currentValue > 0) 195 | // moveNearerToZero now refers to the nested stepForward() function 196 | while currentValue != 0 { 197 | print("\(currentValue)... ") 198 | currentValue = moveNearerToZero(currentValue) 199 | } 200 | print("zero!") 201 | //: [下一页](@next) 202 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Type Casting.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Type-Casting.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 类型转换 10 | /*: 11 | 类型检查可以判断实例的类型,使用 is 操作符实现;类型转换使用 as 操作符实现。使用 is 类型检查还可以用来检查一个类型是否实现了某个协议。 12 | 13 | class MediaItem { 14 | var name: String 15 | init(name: String) { 16 | self.name = name 17 | } 18 | } 19 | 20 | class Movie: MediaItem { 21 | var director: String 22 | init(name: String, director: String) { 23 | self.director = director 24 | super.init(name: name) 25 | } 26 | } 27 | 28 | class Song: MediaItem { 29 | var artist: String 30 | init(name: String, artist: String) { 31 | self.artist = artist 32 | super.init(name: name) 33 | } 34 | } 35 | 36 | let library = [ 37 | Movie(name: "Casablanca", director: "Michael Curtiz"), 38 | Song(name: "Blue Suede Shoes", artist: "Elvis Presley"), 39 | Movie(name: "Citizen Kane", director: "Orson Welles"), 40 | Song(name: "The One And Only", artist: "Chesney Hawkes"), 41 | Song(name: "Never Gonna Give You Up", artist: "Rick Astley") 42 | ] 43 | */ 44 | class MediaItem { 45 | var name: String 46 | init(name: String) { 47 | self.name = name 48 | } 49 | } 50 | 51 | class Movie: MediaItem { 52 | var director: String 53 | init(name: String, director: String) { 54 | self.director = director 55 | super.init(name: name) 56 | } 57 | } 58 | 59 | class Song: MediaItem { 60 | var artist: String 61 | init(name: String, artist: String) { 62 | self.artist = artist 63 | super.init(name: name) 64 | } 65 | } 66 | 67 | let library = [ 68 | Movie(name: "Casablanca", director: "Michael Curtiz"), 69 | Song(name: "Blue Suede Shoes", artist: "Elvis Presley"), 70 | Movie(name: "Citizen Kane", director: "Orson Welles"), 71 | Song(name: "The One And Only", artist: "Chesney Hawkes"), 72 | Song(name: "Never Gonna Give You Up", artist: "Rick Astley") 73 | ] 74 | //: ## 类型检查 75 | /*: 76 | 用类型检查操作符(is)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 true,否则返回 false。 77 | 78 | var movieCount = 0 79 | var songCount = 0 80 | 81 | for item in library { 82 | if item is Movie { 83 | movieCount += 1 84 | } else if item is Song { 85 | songCount += 1 86 | } 87 | } 88 | 89 | print("Media library contains \(movieCount) movies and \(songCount) songs") 90 | // 打印 “Media library contains 2 movies and 3 songs” 91 | 92 | */ 93 | var movieCount = 0 94 | var songCount = 0 95 | 96 | for item in library { 97 | if item is Movie { 98 | movieCount += 1 99 | } else if item is Song { 100 | songCount += 1 101 | } 102 | } 103 | 104 | print("Media library contains \(movieCount) movies and \(songCount) songs") 105 | //: ## 向下转型 106 | /*: 107 | 某类型的一个常量或变量可能在实现上属于一个子类。这种情况可以尝试向下转到它的子类型,用类型转换操作符(as? 或 as!)。向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式 as? 返回一个试图向下转成的类型的可选值。强制形式 as! 把试图向下转型和强制解包转换结果结合为一个操作,当转换失败是会触发运行时错误。 108 | 109 | for item in library { 110 | if let movie = item as? Movie { 111 | print("Movie: '\(movie.name)', dir. \(movie.director)") 112 | } else if let song = item as? Song { 113 | print("Song: '\(song.name)', by \(song.artist)") 114 | } 115 | } 116 | 117 | // Movie: 'Casablanca', dir. Michael Curtiz 118 | // Song: 'Blue Suede Shoes', by Elvis Presley 119 | // Movie: 'Citizen Kane', dir. Orson Welles 120 | // Song: 'The One And Only', by Chesney Hawkes 121 | // Song: 'Never Gonna Give You Up', by Rick Astley 122 | 123 | */ 124 | for item in library { 125 | if let movie = item as? Movie { 126 | print("Movie: '\(movie.name)', dir. \(movie.director)") 127 | } else if let song = item as? Song { 128 | print("Song: '\(song.name)', by \(song.artist)") 129 | } 130 | } 131 | //: ## Any 和 AnyObject 132 | /*: 133 | Swift 为不确定类型提供了两种特殊的类型别名: 134 | - Any 可以表示任何类型,包括函数类型 135 | - AnyObject 可以表示任何类类型的实例 136 | 137 | var things = [Any]() 138 | 139 | things.append(0) 140 | things.append(0.0) 141 | things.append(42) 142 | things.append(3.14159) 143 | things.append("hello") 144 | things.append((3.0, 5.0)) 145 | things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman")) 146 | things.append({ (name: String) -> String in "Hello, \(name)" }) 147 | 148 | 在 switch 表达式的 case 中使用 is 和 as 操作符判断类型: 149 | 150 | for thing in things { 151 | switch thing { 152 | case 0 as Int: 153 | print("zero as an Int") 154 | case 0 as Double: 155 | print("zero as a Double") 156 | case let someInt as Int: 157 | print("an integer value of \(someInt)") 158 | case let someDouble as Double where someDouble > 0: 159 | print("a positive double value of \(someDouble)") 160 | case is Double: 161 | print("some other double value that I don't want to print") 162 | case let someString as String: 163 | print("a string value of \"\(someString)\"") 164 | case let (x, y) as (Double, Double): 165 | print("an (x, y) point at \(x), \(y)") 166 | case let movie as Movie: 167 | print("a movie called '\(movie.name)', dir. \(movie.director)") 168 | case let stringConverter as (String) -> String: 169 | print(stringConverter("Michael")) 170 | default: 171 | print("something else") 172 | } 173 | } 174 | 175 | // zero as an Int 176 | // zero as a Double 177 | // an integer value of 42 178 | // a positive double value of 3.14159 179 | // a string value of "hello" 180 | // an (x, y) point at 3.0, 5.0 181 | // a movie called 'Ghostbusters', dir. Ivan Reitman 182 | // Hello, Michael 183 | 184 | */ 185 | var things = [Any]() 186 | 187 | things.append(0) 188 | things.append(0.0) 189 | things.append(42) 190 | things.append(3.14159) 191 | things.append("hello") 192 | things.append((3.0, 5.0)) 193 | things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman")) 194 | things.append({ (name: String) -> String in "Hello, \(name)" }) 195 | 196 | for thing in things { 197 | switch thing { 198 | case 0 as Int: 199 | print("zero as an Int") 200 | case 0 as Double: 201 | print("zero as a Double") 202 | case let someInt as Int: 203 | print("an integer value of \(someInt)") 204 | case let someDouble as Double where someDouble > 0: 205 | print("a positive double value of \(someDouble)") 206 | case is Double: 207 | print("some other double value that I don't want to print") 208 | case let someString as String: 209 | print("a string value of \"\(someString)\"") 210 | case let (x, y) as (Double, Double): 211 | print("an (x, y) point at \(x), \(y)") 212 | case let movie as Movie: 213 | print("a movie called '\(movie.name)', dir. \(movie.director)") 214 | case let stringConverter as (String) -> String: 215 | print(stringConverter("Michael")) 216 | default: 217 | print("something else") 218 | } 219 | } 220 | //: [下一页](@next) 221 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Extensions.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 扩展 10 | /*: 11 | *扩展*就是为一个已有的类、结构体、枚举类型或者协议类型添加新功能。可以在没有权限获取原始源代码的情况下扩展类型的能力(即 逆向建模 )。扩展和 Objective-C 中的分类类似。(与 Objective-C 不同的是,Swift 的扩展没有名字。) 12 | 13 | Swift 中的扩展可以: 14 | 15 | - 添加计算型属性和计算型类型属性 16 | - 定义实例方法和类型方法 17 | - 提供新的构造器 18 | - 定义下标 19 | - 定义和使用新的嵌套类型 20 | - 使一个已有类型符合某个协议 21 | - 在 Swift 中,甚至可以对协议进行扩展,提供协议要求的实现,或者添加额外的功能,从而可以让符合协议的类型拥有这些功能 22 | 23 | - Note: 24 | 扩展可以为一个类型添加新的功能,但是不能重写已有的功能。 25 | */ 26 | 27 | //: ## 扩展语法 28 | /*: 29 | 使用关键字 extension 来声明扩展: 30 | 31 | extension SomeType { 32 | // 为 SomeType 添加的新功能写到这里 33 | } 34 | 35 | 可以通过扩展来扩展一个已有类型,使其实现一个或多个协议: 36 | 37 | extension SomeType: SomeProtocol, AnotherProctocol { 38 | // 协议实现写到这里 39 | } 40 | 41 | - Note: 42 | 扩展添加的新功能对该类型的所有已有实例都是可用的,即使它们是在这个扩展定义之前创建的。 43 | */ 44 | 45 | //: ## 计算型属性 46 | /*: 47 | 扩展可以为已有类型添加计算型实例属性和计算型类型属性: 48 | 49 | extension Double { 50 | var km: Double { return self * 1_000.0 } 51 | var m : Double { return self } 52 | var cm: Double { return self / 100.0 } 53 | var mm: Double { return self / 1_000.0 } 54 | var ft: Double { return self / 3.28084 } 55 | } 56 | let oneInch = 25.4.mm 57 | print("One inch is \(oneInch) meters") 58 | // 打印 “One inch is 0.0254 meters” 59 | let threeFeet = 3.ft 60 | print("Three feet is \(threeFeet) meters") 61 | // 打印 “Three feet is 0.914399970739201 meters” 62 | 63 | let aMarathon = 42.km + 195.m 64 | print("A marathon is \(aMarathon) meters long") 65 | // 打印 “A marathon is 42195.0 meters long” 66 | 67 | - Note: 68 | 扩展可以添加新的计算型属性,但是不可以添加存储型属性,也不可以为已有属性添加属性观察器。 69 | */ 70 | extension Double { 71 | var km: Double { return self * 1_000.0 } 72 | var m : Double { return self } 73 | var cm: Double { return self / 100.0 } 74 | var mm: Double { return self / 1_000.0 } 75 | var ft: Double { return self / 3.28084 } 76 | } 77 | let oneInch = 25.4.mm 78 | print("One inch is \(oneInch) meters") 79 | 80 | let threeFeet = 3.ft 81 | print("Three feet is \(threeFeet) meters") 82 | 83 | let aMarathon = 42.km + 195.m 84 | print("A marathon is \(aMarathon) meters long") 85 | //: ## 构造器 86 | /*: 87 | 扩展可以为已有类型添加新的构造器,对于类类型而言,则只能添加便利构造器,不能添加指定构造器和析构器,这两者只能定义在类的定义里边: 88 | 89 | struct Size { 90 | var width = 0.0, height = 0.0 91 | } 92 | struct Point { 93 | var x = 0.0, y = 0.0 94 | } 95 | struct Rect { 96 | var origin = Point() 97 | var size = Size() 98 | } 99 | 100 | let defaultRect = Rect() 101 | let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0), 102 | size: Size(width: 5.0, height: 5.0)) 103 | 104 | extension Rect { 105 | init(center: Point, size: Size) { 106 | let originX = center.x - (size.width / 2) 107 | let originY = center.y - (size.height / 2) 108 | self.init(origin: Point(x: originX, y: originY), size: size) 109 | } 110 | } 111 | 112 | let centerRect = Rect(center: Point(x: 4.0, y: 4.0), 113 | size: Size(width: 3.0, height: 3.0)) 114 | // centerRect 的原点是 (2.5, 2.5),大小是 (3.0, 3.0) 115 | */ 116 | struct Size { 117 | var width = 0.0, height = 0.0 118 | } 119 | struct Point { 120 | var x = 0.0, y = 0.0 121 | } 122 | struct Rect { 123 | var origin = Point() 124 | var size = Size() 125 | } 126 | 127 | let defaultRect = Rect() 128 | let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0), 129 | size: Size(width: 5.0, height: 5.0)) 130 | 131 | extension Rect { 132 | init(center: Point, size: Size) { 133 | let originX = center.x - (size.width / 2) 134 | let originY = center.y - (size.height / 2) 135 | self.init(origin: Point(x: originX, y: originY), size: size) 136 | } 137 | } 138 | 139 | let centerRect = Rect(center: Point(x: 4.0, y: 4.0), 140 | size: Size(width: 3.0, height: 3.0)) 141 | 142 | //: ## 方法 143 | /*: 144 | 扩展可以为已有类型添加新的实例方法和类型方法: 145 | 146 | extension Int { 147 | func repetitions(task: () -> Void) { 148 | for _ in 0.. Void) { 173 | for _ in 0.. Int { 196 | var decimalBase = 1 197 | for _ in 0.. Int { 215 | var decimalBase = 1 216 | for _ in 0.. 0: 239 | return .positive 240 | default: 241 | return .negative 242 | } 243 | } 244 | } 245 | 246 | func printIntegerKinds(_ numbers: [Int]) { 247 | for number in numbers { 248 | switch number.kind { 249 | case .negative: 250 | print("- ", terminator: "") 251 | case .zero: 252 | print("0 ", terminator: "") 253 | case .positive: 254 | print("+ ", terminator: "") 255 | } 256 | } 257 | print("") 258 | } 259 | printIntegerKinds([3, 19, -27, 0, -6, 0, 7]) 260 | // 打印 “+ + - 0 - 0 + ” 261 | 262 | */ 263 | extension Int { 264 | enum Kind { 265 | case negative, zero, positive 266 | } 267 | var kind: Kind { 268 | switch self { 269 | case 0: 270 | return .zero 271 | case let x where x > 0: 272 | return .positive 273 | default: 274 | return .negative 275 | } 276 | } 277 | } 278 | 279 | func printIntegerKinds(_ numbers: [Int]) { 280 | for number in numbers { 281 | switch number.kind { 282 | case .negative: 283 | print("- ", terminator: "") 284 | case .zero: 285 | print("0 ", terminator: "") 286 | case .positive: 287 | print("+ ", terminator: "") 288 | } 289 | } 290 | print("") 291 | } 292 | printIntegerKinds([3, 19, -27, 0, -6, 0, 7]) 293 | //: [下一页](@next) 294 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Properties.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Properties.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 属性 10 | /*: 11 | 属性将值跟特定的类、结构或枚举关联。存储属性存储常量或变量作为实例的一部分,而计算属性计算(不是存储)一个值。计算属性可以用于类、结构体和枚举,存储属性只能用于类和结构体。 12 | 13 | 存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接作用于类型本身,这种属性称为类型属性。 14 | 15 | 另外,还可以定义属性观察器来监控属性值的变化,以此来触发一个自定义的操作。属性观察器可以添加到自己定义的存储属性上,也可以添加到从父类继承的属性上。 16 | */ 17 | 18 | //: ## 存储属性 19 | /*: 20 | 一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。存储属性可以是变量存储属性(用关键字 var 定义),也可以是常量存储属性(用关键字 let 定义)。 21 | 22 | 可以在定义存储属性的时候指定默认值,也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值。 23 | 24 | struct FixedLengthRange { 25 | var firstValue: Int 26 | let length: Int 27 | } 28 | var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3) 29 | // 该区间表示整数0,1,2 30 | rangeOfThreeItems.firstValue = 6 31 | // 该区间现在表示整数6,7,8 32 | 33 | */ 34 | 35 | //: ### 常量结构体的存储属性 36 | /*: 37 | 如果创建了一个结构体的实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使有属性被声明为变量也不行: 38 | 39 | let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4) 40 | // 该区间表示整数0,1,2,3 41 | rangeOfFourItems.firstValue = 6 42 | // 尽管 firstValue 是个变量属性,这里还是会报错 43 | 44 | 这种行为是由于结构体(struct)属于值类型,当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。 45 | 46 | 属于引用类型的类(class)则不一样,把一个引用类型的实例赋给一个常量后,仍然可以修改该实例的变量属性。 47 | */ 48 | 49 | //: ## 延迟存储属性 50 | /*: 51 | 延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 lazy 来标示一个延迟存储属性。 52 | - Note: 53 | 必须将延迟存储属性声明成变量(使用 var 关键字),因为属性的初始值可能在实例构造完成之后才会计算;而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。 54 | 55 | 56 | class DataImporter { 57 | /* 58 | DataImporter 是一个负责将外部文件中的数据导入的类。 59 | 这个类的初始化会消耗不少时间。 60 | */ 61 | var fileName = "data.txt" 62 | // 这里会提供数据导入功能 63 | } 64 | 65 | class DataManager { 66 | lazy var importer = DataImporter() 67 | var data = [String]() 68 | // 这里会提供数据管理功能 69 | } 70 | 71 | let manager = DataManager() 72 | manager.data.append("Some data") 73 | manager.data.append("Some more data") 74 | // DataImporter 实例的 importer 属性还没有被创建 75 | 76 | - Note: 77 | 如果一个被标记为 lazy 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次;也就是说 lazy 属性是非线程安全的! 78 | */ 79 | 80 | //: ## 存储属性和实例变量 81 | /*: 82 | Swift 中的属性没有对应的实例变量,属性的后端存储也无法直接访问,避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。属性的全部信息——包括命名、类型和内存管理特征——作为类型定义的一部分,都定义在一个地方。 83 | */ 84 | 85 | //: ## 计算属性 86 | /*: 87 | 除存储属性外,**类、结构体和枚举**可以定义计算属性。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值: 88 | 89 | struct Point { 90 | var x = 0.0, y = 0.0 91 | } 92 | struct Size { 93 | var width = 0.0, height = 0.0 94 | } 95 | struct Rect { 96 | var origin = Point() 97 | var size = Size() 98 | var center: Point { 99 | get { 100 | let centerX = origin.x + (size.width / 2) 101 | let centerY = origin.y + (size.height / 2) 102 | return Point(x: centerX, y: centerY) 103 | } 104 | set(newCenter) { 105 | origin.x = newCenter.x - (size.width / 2) 106 | origin.y = newCenter.y - (size.height / 2) 107 | } 108 | } 109 | } 110 | var square = Rect(origin: Point(x: 0.0, y: 0.0), 111 | size: Size(width: 10.0, height: 10.0)) 112 | let initialSquareCenter = square.center 113 | square.center = Point(x: 15.0, y: 15.0) 114 | print("square.origin is now at (\(square.origin.x), \(square.origin.y))") 115 | // 打印 "square.origin is now at (10.0, 10.0)” 116 | 117 | */ 118 | 119 | //: ### 简化 Setter 声明 120 | /*: 121 | 如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 newValue : 122 | 123 | struct AlternativeRect { 124 | var origin = Point() 125 | var size = Size() 126 | var center: Point { 127 | get { 128 | let centerX = origin.x + (size.width / 2) 129 | let centerY = origin.y + (size.height / 2) 130 | return Point(x: centerX, y: centerY) 131 | } 132 | set { 133 | origin.x = newValue.x - (size.width / 2) 134 | origin.y = newValue.y - (size.height / 2) 135 | } 136 | } 137 | } 138 | 139 | */ 140 | 141 | //: ## 只读计算属性 142 | /*: 143 | 只有 getter 没有 setter 的计算属性就是只读计算属性。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。 144 | - Note: 145 | 必须使用 var 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let 关键字只用来声明常量属性,表示初始化后再也无法修改的值。 146 | 147 | 只读计算属性的声明可以去掉 get 关键字和花括号: 148 | 149 | struct Cuboid { 150 | var width = 0.0, height = 0.0, depth = 0.0 151 | var volume: Double { 152 | return width * height * depth 153 | } 154 | } 155 | let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0) 156 | print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)") 157 | // 打印 "the volume of fourByFiveByTwo is 40.0" 158 | 159 | */ 160 | 161 | //: ## 属性观察器 162 | /*: 163 | 属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。 164 | 165 | 属性观察器分两类: 166 | - willSet 在新的值被设置之前调用,willSet 会将新的属性值作为常量参数传入,在 willSet 的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称 newValue 表示。 167 | - didSet 在新的值被设置之后立即调用,didSet 会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名 oldValue。如果在 didSet 方法中再次对该属性赋值,那么新值会覆盖旧的值。 168 | 169 | - Note: 170 | 父类的属性在子类的构造器中被赋值时,它在父类中的 willSet 和 didSet 观察器会被调用,随后才会调用子类的观察器。在父类初始化方法调用之前,子类给属性赋值时,观察器不会被调用。 171 | 172 | class StepCounter { 173 | var totalSteps: Int = 0 { 174 | willSet(newTotalSteps) { 175 | print("About to set totalSteps to \(newTotalSteps)") 176 | } 177 | didSet { 178 | if totalSteps > oldValue { 179 | print("Added \(totalSteps - oldValue) steps") 180 | } 181 | } 182 | } 183 | } 184 | let stepCounter = StepCounter() 185 | stepCounter.totalSteps = 200 186 | // About to set totalSteps to 200 187 | // Added 200 steps 188 | stepCounter.totalSteps = 360 189 | // About to set totalSteps to 360 190 | // Added 160 steps 191 | stepCounter.totalSteps = 896 192 | // About to set totalSteps to 896 193 | // Added 536 steps 194 | 195 | */ 196 | 197 | //: ## 全局变量和局部变量 198 | /*: 199 | 计算属性和属性观察器所描述的功能也可以用于全局变量和局部变量。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。 200 | 201 | 在全局或局部范围都可以定义计算型变量和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也完全一样。 202 | 203 | - Note: 204 | 全局的常量或变量都是延迟计算的,跟延迟存储属性相似,不同的地方在于,全局的常量或变量不需要标记 lazy 修饰符。 205 | 局部范围的常量或变量从不延迟计算。 206 | */ 207 | 208 | //: ## 类型属性 209 | /*: 210 | 实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。 211 | 212 | 可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是类型属性。 213 | 214 | 存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算型属性一样只能定义成变量属性。 215 | 216 | - Note: 217 | 跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。 218 | 存储型类型属性是**延迟**初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用 lazy 修饰符。 219 | */ 220 | 221 | //: ### 类型属性语法 222 | /*: 223 | Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。 224 | 225 | 使用关键字 static 来定义类型属性。在为类定义计算型类型属性时,可以改用关键字 class 来支持子类对父类的实现进行重写: 226 | 227 | struct SomeStructure { 228 | static var storedTypeProperty = "Some value." 229 | static var computedTypeProperty: Int { 230 | return 1 231 | } 232 | } 233 | enum SomeEnumeration { 234 | static var storedTypeProperty = "Some value." 235 | static var computedTypeProperty: Int { 236 | return 6 237 | } 238 | } 239 | class SomeClass { 240 | static var storedTypeProperty = "Some value." 241 | static var computedTypeProperty: Int { 242 | return 27 243 | } 244 | class var overrideableComputedTypeProperty: Int { 245 | return 107 246 | } 247 | } 248 | 249 | */ 250 | //: [下一页](@next) 251 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Collection Types.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Collection-Types.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | 10 | //: # 集合类型 11 | /*: 12 | Swift 语言提供 Arrays、Sets 和 Dictionaries 三种基本的集合类型用来存储集合数据。数组(Arrays)是有序数据的集。集合(Sets)是无序无重复数据的集。字典(Dictionaries)是无序的键值对的集。 13 | 14 | Swift 语言中的 Arrays、Sets 和 Dictionaries 中存储的数据值类型必须明确,不能把错误的数据类型插入其中。 15 | 16 | - Note: 17 | Swift 的 Arrays、Sets 和 Dictionaries 类型均是以泛型实现的。 18 | 19 | ## 集合可变性 20 | 创建一个变量的 Arrays、Sets 或 Dictionaries ,这个集合就是可变的。创建一个常量 Arrays、Sets 或 Dictionaries ,那么它就是不可变的,大小和内容都不能被改变。 21 | */ 22 | 23 | //: ## 数组 24 | /*: 25 | 数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。 Swift 中的 Array 类型可以和 Foundation 中的 NSArray 相互桥接使用。 26 | */ 27 | //: ### 数组的语法 28 | /*: 29 | Swift 数组声明方式为 Array ,其中 Element 是这个数组中唯一允许存在的数据类型。同时,也可以使用像 [Element] 这样的简单语法。 30 | */ 31 | //: ### 创建空数组 32 | /*: 33 | 创建空数组有两种方式,一种是直接调用初始换函数,另一种是使用空数组字面量: 34 | 35 | var someInts = [Int]() 36 | print("someInts is of type [Int] with \(someInts.count) items.") 37 | // 打印 "someInts is of type [Int] with 0 items." 38 | 39 | someInts.append(3) 40 | // someInts 现在包含一个 Int 值 41 | someInts = [] 42 | // someInts 现在是空数组,但是仍然是 [Int] 类型的。 43 | 44 | */ 45 | //: ### 创建带默认值的数组 46 | /*: 47 | Swift 中的 Array 类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。我们可以把准备加入新数组的数据数量(count)和适当类型的初始值(repeating)传入数组构造函数: 48 | 49 | var threeDoubles = Array(repeating: 0.0, count: 3) 50 | // threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0] 51 | 52 | */ 53 | //: ### 使用数组字面量 54 | /*: 55 | 可以使用数组字面量来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。数组字面量是一系列由逗号分割并由方括号包含的数值: 56 | 57 | [value 1, value 2, value 3] 58 | 59 | var shoppingList: [String] = ["Eggs", "Milk"] 60 | // shoppingList 已经被构造并且拥有两个初始项。 61 | 62 | 可以省略类型声明, Swift 会自动推断类型: 63 | 64 | var shoppingList = ["Eggs", "Milk"] 65 | 66 | */ 67 | //: ## 集合 68 | /*: 69 | 集合(Set)用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。 Swift 中的 Set 类型可以和 Foundation 中的 NSSet 相互桥接使用。 70 | */ 71 | //: ### 集合类型的哈希值 72 | /*: 73 | 一个类型为了存储在集合中,该类型必须是可哈希化的--也就是说,该类型必须提供一个方法来计算它的哈希值。一个哈希值是 Int 类型的,相等的对象哈希值必须相同,比如 a==b,因此必须 a.hashValue == b.hashValue。 74 | 75 | Swift 的所有基本类型(比如 String,Int,Double 和 Bool)默认都是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值默认也是可哈希化的。 76 | 77 | 可以使用自定义的类型作为集合的值的类型或者是字典的键的类型,但你需要使你的自定义类型符合 Swift 标准库中的 Hashable 协议。 78 | */ 79 | //: ### 集合类型语法 80 | /*: 81 | Swift 中的 Set 类型声明为 Set,这里的 Element 表示 Set 中允许存储的类型。 82 | */ 83 | //: ### 创建和构造一个空的集合 84 | /*: 85 | 可以通过构造器语法创建一个特定类型的空集合: 86 | 87 | var letters = Set() 88 | print("letters is of type Set with \(letters.count) items.") 89 | // 打印 "letters is of type Set with 0 items." 90 | 91 | 可以通过一个空的数组字面量创建一个空的 Set: 92 | 93 | letters.insert("a") 94 | // letters 现在含有1个 Character 类型的值 95 | letters = [] 96 | // letters 现在是一个空的 Set, 但是它依然是 Set 类型 97 | 98 | */ 99 | //: ### 用数组字面量创建集合 100 | /*: 101 | 可以使用数组字面量来构造集合: 102 | 103 | var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] 104 | // favoriteGenres 被构造成含有三个初始值的集合 105 | 106 | Set 类型不能从数组字面量中被单独推断出来,因此 Set 类型必须显式声明: 107 | 108 | var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] 109 | 110 | */ 111 | //: ### 集合操作 112 | /*: 113 | 可以高效地完成 Set 的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。 114 | */ 115 | //: ### 基本集合操作 116 | /*: 117 | - 使用 intersection(_:) 方法根据两个集合中都包含的值创建的一个新的集合。 118 | - 使用 symmetricDifference(_:) 方法根据在一个集合中但不在两个集合中的值创建一个新的集合。 119 | - 使用 union(_:) 方法根据两个集合的值创建一个新的集合。 120 | - 使用 subtracting(_:) 方法根据不在该集合中的值创建一个新的集合。 121 | 122 | 示例: 123 | 124 | let oddDigits: Set = [1, 3, 5, 7, 9] 125 | let evenDigits: Set = [0, 2, 4, 6, 8] 126 | let singleDigitPrimeNumbers: Set = [2, 3, 5, 7] 127 | 128 | oddDigits.union(evenDigits).sorted() 129 | // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 130 | oddDigits. intersection(evenDigits).sorted() 131 | // [] 132 | oddDigits.subtracting(singleDigitPrimeNumbers).sorted() 133 | // [1, 9] 134 | oddDigits. symmetricDifference(singleDigitPrimeNumbers).sorted() 135 | // [1, 2, 9] 136 | 137 | */ 138 | //: ### 集合成员关系和相等 139 | /*: 140 | - 使用“是否相等”运算符(==)来判断两个集合是否包含全部相同的值。 141 | - 使用 isSubset(of:) 方法来判断一个集合中的值是否也被包含在另外一个集合中。 142 | - 使用 isSuperset(of:) 方法来判断一个集合中包含另一个集合中所有的值。 143 | - 使用 isStrictSubset(of:) 或者 isStrictSuperset(of:) 方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。 144 | - 使用 isDisjoint(with:) 方法来判断两个集合是否不含有相同的值(是否没有交集)。 145 | 146 | 示例: 147 | 148 | let houseAnimals: Set = ["🐶", "🐱"] 149 | let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"] 150 | let cityAnimals: Set = ["🐦", "🐭"] 151 | 152 | houseAnimals.isSubset(of: farmAnimals) 153 | // true 154 | farmAnimals.isSuperset(of: houseAnimals) 155 | // true 156 | farmAnimals.isDisjoint(with: cityAnimals) 157 | // true 158 | 159 | */ 160 | //: ## 字典 161 | /*: 162 | 字典是一种存储多个相同类型的值的容器。每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。 Swift 中的 Dictionary 类型可以和 Foundation 中的 NSDictionary 相互桥接使用。 163 | */ 164 | //: ### 字典类型声明 165 | /*: 166 | Swift 的字典使用 Dictionary 定义,其中 Key 是字典中键的数据类型,Value 是字典中对应于这些键所存储值的数据类型,用 [Key: Value] 简化声明。字典的 Key 类型必须遵循 Hashable 协议。 167 | */ 168 | //: ### 创建空字典 169 | /*: 170 | 可以像数组一样使用构造语法创建一个拥有确定类型的空字典: 171 | 172 | var namesOfIntegers = [Int: String]() 173 | // namesOfIntegers 是一个空的 [Int: String] 字典 174 | 175 | namesOfIntegers[16] = "sixteen" 176 | // namesOfIntegers 现在包含一个键值对 177 | namesOfIntegers = [:] 178 | // namesOfIntegers 又成为了一个 [Int: String] 类型的空字典 179 | 180 | */ 181 | //: ### 用字面量创建字典 182 | /*: 183 | 可以使用字典字面量来构造字典,字典字面量是一种将一个或多个键值对写作 Dictionary 集合简写方式: 184 | 185 | [key 1: value 1, key 2: value 2, key 3: value 3] 186 | 187 | 如: 188 | 189 | var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] 190 | 191 | 可以使用类型推断: 192 | 193 | var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] 194 | 195 | */ 196 | //: ### 访问和修改集合类型 197 | /*: 198 | 可以通过集合类型的方法和属性来访问和修改集合类型,或者使用下标语法。获取数量: 199 | 200 | print("The shopping list contains \(shoppingList.count) items.") 201 | // 输出 "The shopping list contains 2 items."(这个数组有2个项) 202 | 203 | 使用布尔属性 isEmpty 检测数组是否为空;如果只需要判断一个数字是否为空,尽量使用 isEmpty 来判断,使用 count 判断会遍历数组, isEmpty 不会发生遍历,而是判断数组的首尾指针是否相等,效率更快: 204 | 205 | if shoppingList.isEmpty { 206 | print("The shopping list is empty.") 207 | } else { 208 | print("The shopping list is not empty.") 209 | } 210 | // 打印 "The shopping list is not empty."(shoppinglist 不是空的) 211 | 212 | 使用 append(_:) 方法添加数据: 213 | 214 | shoppingList.append("Flour") 215 | // shoppingList 现在有3个数据项,有人在摊煎饼 216 | 217 | 使用运算符添加数据: 218 | 219 | shoppingList += ["Baking Powder"] 220 | // shoppingList 现在有四项了 221 | shoppingList += ["Chocolate Spread", "Cheese", "Butter"] 222 | // shoppingList 现在有七项了 223 | 224 | 使用下标语法来获取数组中的数据: 225 | 226 | var firstItem = shoppingList[0] 227 | // 第一项是 "Eggs" 228 | 229 | 使用下标来改变某个已有索引值对应的数据: 230 | 231 | shoppingList[0] = "Six eggs" 232 | // 其中的第一项现在是 "Six eggs" 而不是 "Eggs" 233 | 234 | shoppingList[4...6] = ["Bananas", "Apples"] 235 | // shoppingList 现在有6项 236 | 237 | 调用数组的 insert(_:at:) 方法来插入数据: 238 | 239 | shoppingList.insert("Maple Syrup", at: 0) 240 | // shoppingList 现在有7项 241 | // "Maple Syrup" 现在是这个列表中的第一项 242 | 243 | 使用 remove(at:) 方法来移除数据: 244 | 245 | let mapleSyrup = shoppingList.remove(at: 0) 246 | // 索引值为0的数据项被移除 247 | // shoppingList 现在只有6项,而且不包括 Maple Syrup 248 | // mapleSyrup 常量的值等于被移除数据项的值 "Maple Syrup" 249 | 250 | let apples = shoppingList.removeLast() 251 | // 数组的最后一项被移除了 252 | // shoppingList 现在只有5项,不包括 Apples 253 | // apples 常量的值现在等于 "Apples" 字符串 254 | 255 | */ 256 | //: ## 遍历集合类型 257 | /*: 258 | 可以使用 for-in 循环来遍历: 259 | 260 | 261 | 数组和 Set : 262 | 263 | for item in shoppingList { 264 | print(item) 265 | } 266 | // Six eggs 267 | // Milk 268 | // Flour 269 | // Baking Powder 270 | // Bananas 271 | 272 | 字典: 273 | 274 | for (airportCode, airportName) in airports { 275 | print("\(airportCode): \(airportName)") 276 | } 277 | // YYZ: Toronto Pearson 278 | // LHR: London Heathrow 279 | 280 | 使用 enumerated() 方法来获取数组的索引和值: 281 | 282 | for (index, value) in shoppingList. enumerated() { 283 | print("Item \(String(index + 1)): \(value)") 284 | } 285 | // Item 1: Six eggs 286 | // Item 2: Milk 287 | // Item 3: Flour 288 | // Item 4: Baking Powder 289 | // Item 5: Bananas 290 | 291 | */ 292 | //: ## 内建方法 293 | /*: 294 | 所有集合类型都实现了 Sequence 协议, Sequence 协议作为标准库类型,定义了一系列内建的函数和属性,最常见的就是一系列的集合操作,而且多数都是函数式编程的思想在里边, [Swquence](https://developer.apple.com/documentation/swift/sequence) 定义。 295 | */ 296 | //: [下一页](@next) 297 | let arr = [1, 2] 298 | for item in arr.reversed() { 299 | 300 | } 301 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Control Flow.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Control-Flow.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | 10 | //: # 控制流 11 | /*: 12 | `Swift` 提供了多种流程控制结构,包括 `while` 循环,条件控制 `if`、`guard` 和 `switch` 语句,还有控制流程跳转的 `break` 和 `continue` 语句。 13 | 14 | `Swift` 还提供了 `for-in` 循环,用来更简单地遍历数组 `Array` ,字典 `Dictionary` ,区间 `Range` ,字符串 `String` 和其他序列类型。 15 | 16 | `Swift` 的 `switch` 语句比 `C` 语言中更加强大。`case` 可以匹配很多不同的模式,包括范围匹配,元组 `tuple` 和特定类型匹配。`switch` 语句的 `case` 中匹配的值可以声明为临时常量或变量,在 `case` 作用域内使用,也可以配合 `where` 来描述更复杂的匹配条件。 17 | */ 18 | 19 | //: ## For-In 循环 20 | /*: 21 | `Swift` 中使用 for-in 循环来遍历一个集合中的所有元素: 22 | 23 | let names = ["Anna", "Alex", "Brian", "Jack"] 24 | for name in names { 25 | print("Hello, \(name)!") 26 | } 27 | // Hello, Anna! 28 | // Hello, Alex! 29 | // Hello, Brian! 30 | // Hello, Jack! 31 | 32 | 遍历字典,字典的每项元素会以 (`key`, `value`) 元组的形式返回,可以在 `for-in` 循环中使用显式的常量名称来解读 (`key`, `value`) 元组: 33 | 34 | let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] 35 | for (animalName, legCount) in numberOfLegs { 36 | print("\(animalName)s have \(legCount) legs") 37 | } 38 | // ants have 6 legs 39 | // spiders have 8 legs 40 | // cats have 4 legs 41 | 42 | 遍历区间: 43 | 44 | for index in 1...5 { 45 | print("\(index) times 5 is \(index * 5)") 46 | } 47 | // 1 times 5 is 5 48 | // 2 times 5 is 10 49 | // 3 times 5 is 15 50 | // 4 times 5 is 20 51 | // 5 times 5 is 25 52 | 53 | */ 54 | 55 | //: ## While 循环 56 | /*: 57 | `while` 循环会一直运行一段语句直到条件变成 `false` 。`Swift` 提供两种 `while` 循环形式: 58 | 59 | - `while` 循环,每次在循环开始时计算条件是否符合; 60 | 61 | while condition { 62 | statements 63 | } 64 | 65 | - `repeat-while` 循环,每次在循环结束时计算条件是否符合。 66 | 67 | repeat { 68 | statements 69 | } while condition 70 | 71 | */ 72 | 73 | //: ## 条件判断 74 | /*: 75 | `Swift` 提供两种类型的条件语句:`if` 语句和 `switch` 语句。当条件较为简单且可能的情况很少时,使用 `if` 语句。 `switch` 语句适用于条件较复杂、有更多排列组合的时候, `switch` 还可以使用复杂的模式匹配(`pattern-matching`)。 76 | - `if` 语句最简单的形式就是只包含一个条件,只有该条件为 `true` 时,才执行相关代码: 77 | 78 | var temperatureInFahrenheit = 30 79 | if temperatureInFahrenheit <= 32 { 80 | print("It's very cold. Consider wearing a scarf.") 81 | } 82 | // 输出 "It's very cold. Consider wearing a scarf." 83 | 84 | - `switch` 语句会尝试把值与若干个模式(`pattern`)进行匹配。 `switch` 语句会执行第一个匹配成功的 `case` 的代码: 85 | 86 | switch some value to consider { 87 | case value 1: 88 | respond to value 1 89 | case value 2, value 3: 90 | respond to value 2 or 3 91 | default: 92 | otherwise, do something else 93 | } 94 | 95 | `switch` 语句必须是完备的,当 `case` 无法穷举时,必须提供 `default` 分支。 `switch` 中的 `case` 必须至少包含一条语句。 96 | 97 | let anotherCharacter: Character = "a" 98 | switch anotherCharacter { 99 | case "a": // 无效,这个分支下面没有语句 100 | case "A": 101 | print("The letter A") 102 | default: 103 | print("Not the letter A") 104 | } 105 | // 这段代码会报编译错误 106 | */ 107 | 108 | //: ### 不存在隐式的贯穿 109 | /*: 110 | `Swift` 中 `switch` `case` 中不需要显式的 `break`,`Swift` 中 `switch` 在执行完毕 `case` 之后会默认 `break` ,若需要自动流转到下一个 `case` 则需要使用 `fallthrough` 关键词! 111 | */ 112 | 113 | //: ### 模式匹配:区间 114 | /*: 115 | `case` 分支的模式可以是一个值的区间: 116 | 117 | let approximateCount = 62 118 | let countedThings = "moons orbiting Saturn" 119 | let naturalCount: String 120 | switch approximateCount { 121 | case 0: 122 | naturalCount = "no" 123 | case 1..<5: 124 | naturalCount = "a few" 125 | case 5..<12: 126 | naturalCount = "several" 127 | case 12..<100: 128 | naturalCount = "dozens of" 129 | case 100..<1000: 130 | naturalCount = "hundreds of" 131 | default: 132 | naturalCount = "many" 133 | } 134 | print("There are \(naturalCount) \(countedThings).") 135 | // 输出 "There are dozens of moons orbiting Saturn." 136 | 137 | */ 138 | 139 | //: ### 模式匹配:元组 140 | /*: 141 | `case` 可以使用元组在同一个 `switch` 语句中测试多个值。元组中的元素可以是值,也可以是区间。使用下划线(_)来进行通配: 142 | 143 | let somePoint = (1, 1) 144 | switch somePoint { 145 | case (0, 0): 146 | print("\(somePoint) is at the origin") 147 | case (_, 0): 148 | print("\(somePoint) is on the x-axis") 149 | case (0, _): 150 | print("\(somePoint) is on the y-axis") 151 | case (-2...2, -2...2): 152 | print("\(somePoint) is inside the box") 153 | default: 154 | print("\(somePoint) is outside of the box") 155 | } 156 | // 输出 "(1, 1) is inside the box" 157 | 158 | */ 159 | 160 | //: ### 模式匹配:`where` 161 | /*: 162 | `case` 分支的模式可以使用 `where` 语句来判断额外的条件: 163 | 164 | let yetAnotherPoint = (1, -1) 165 | switch yetAnotherPoint { 166 | case let (x, y) where x == y: 167 | print("(\(x), \(y)) is on the line x == y") 168 | case let (x, y) where x == -y: 169 | print("(\(x), \(y)) is on the line x == -y") 170 | case let (x, y): 171 | print("(\(x), \(y)) is just some arbitrary point") 172 | } 173 | // 输出 "(1, -1) is on the line x == -y" 174 | 175 | */ 176 | 177 | //: ### 模式匹配:复合型case 178 | /*: 179 | 当多个条件可以使用同一种方法来处理时,可以将这几种可能放在同一个 `case` 后面,并且用逗号隔开。当 `case` 后面的任意一种模式匹配的时候,这条分支就会被匹配。并且,如果匹配列表过长,还可以分行书写: 180 | 181 | let someCharacter: Character = "e" 182 | switch someCharacter { 183 | case "a", "e", "i", "o", "u": 184 | print("\(someCharacter) is a vowel") 185 | case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", 186 | "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z": 187 | print("\(someCharacter) is a consonant") 188 | default: 189 | print("\(someCharacter) is not a vowel or a consonant") 190 | } 191 | // 输出 "e is a vowel" 192 | 193 | */ 194 | 195 | //: ## 流程跳转 196 | /*: 197 | 控制转移语句改变代码的执行顺序,通过它可以实现代码的跳转。`Swift` 有五种控制转移语句: 198 | - `continue`:告诉一个循环体立刻停止本次循环,重新开始下次循环 199 | 200 | let puzzleInput = "great minds think alike" 201 | var puzzleOutput = "" 202 | for character in puzzleInput { 203 | switch character { 204 | case "a", "e", "i", "o", "u", " ": 205 | continue 206 | default: 207 | puzzleOutput.append(character) 208 | } 209 | } 210 | print(puzzleOutput) 211 | // 输出 "grtmndsthnklk" 212 | 213 | - `break`:立刻结束整个控制流的执行 214 | 215 | let numberSymbol: Character = "三" // 简体中文里的数字 3 216 | var possibleIntegerValue: Int? 217 | switch numberSymbol { 218 | case "1", "١", "一", "๑": 219 | possibleIntegerValue = 1 220 | case "2", "٢", "二", "๒": 221 | possibleIntegerValue = 2 222 | case "3", "٣", "三", "๓": 223 | possibleIntegerValue = 3 224 | case "4", "٤", "四", "๔": 225 | possibleIntegerValue = 4 226 | default: 227 | break 228 | } 229 | if let integerValue = possibleIntegerValue { 230 | print("The integer value of \(numberSymbol) is \(integerValue).") 231 | } else { 232 | print("An integer value could not be found for \(numberSymbol).") 233 | } 234 | // 输出 "The integer value of 三 is 3." 235 | 236 | - `fallthrough`:在 `Swift` 里,`switch` 语句不会从上一个 `case` 分支跳转到下一个 `case` 分支中,使用 `fallthrough` 可以让代码自动跳到下一个 `case` 执行: 237 | 238 | let integerToDescribe = 5 239 | var description = "The number \(integerToDescribe) is" 240 | switch integerToDescribe { 241 | case 2, 3, 5, 7, 11, 13, 17, 19: 242 | description += " a prime number, and also" 243 | fallthrough 244 | default: 245 | description += " an integer." 246 | } 247 | print(description) 248 | // 输出 "The number 5 is a prime number, and also an integer." 249 | 250 | - `return` 251 | - `throw` 252 | */ 253 | 254 | //: ## 提前退出 255 | /*: 256 | `guard` 语句类似 `if` 语句但有所区别, `guard` 语句当条件为真时执行 `guard` 语句后的代码, `guard` 语句总是有一个 `else` 分支,如果条件为假则执行 `else` 从句中的代码: 257 | 258 | func greet(person: [String: String]) { 259 | guard let name = person["name"] else { 260 | return 261 | } 262 | print("Hello \(name)") 263 | guard let location = person["location"] else { 264 | print("I hope the weather is nice near you.") 265 | return 266 | } 267 | print("I hope the weather is nice in \(location).") 268 | } 269 | greet(["name": "John"]) 270 | // 输出 "Hello John!" 271 | // 输出 "I hope the weather is nice near you." 272 | greet(["name": "Jane", "location": "Cupertino"]) 273 | // 输出 "Hello Jane!" 274 | // 输出 "I hope the weather is nice in Cupertino." 275 | 276 | */ 277 | 278 | //: ## 检测 API 可用性 279 | /*: 280 | `Swift` 内置支持检查 `API` 可用性,这可以确保我们不会在当前部署机器上,不小心地使用了不可用的 `API` : 281 | 282 | if #available(iOS 10, macOS 10.12, *) { 283 | // 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API 284 | } else { 285 | // 使用先前版本的 iOS 和 macOS 的 API 286 | } 287 | 288 | */ 289 | //: [下一页](@next) 290 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Dynamic.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import Foundation 4 | import ObjectiveC 5 | 6 | //: # Dynamic Swift 7 | /*: 8 | 众所周知,Swift 是一门静态语言,所有属性在编译期就已经确定下来了,对方法的派发是是静态完成的,类似于 C++ 的 vtable ,这样可以大幅提升 Swift 的性能,据统计,Swift 静态派发的的速度大约耗时为 `1.1` 纳秒,而 ObjC 的动态派发耗时为 `4.9` 纳秒,Swift 快大约 5 倍的时间,所以 Swift 性能比 ObjC 好,也就是因为 Swift 做了很多静态优化。 9 | 10 | 我们都知道,ObjC 跟 Swift 相反,ObjC 是动态语言,所有方法、属性都是动态派发和动态绑定的,这样给我们带来很多好处,比如我们可以全局的替换一个方法,或者在运行时替换示例的类型等等。在使用 ObjC 的时候,我们可以轻易的实现 AOP 编程。 11 | 12 | 我一度认为,Swift 真的就是一门静态语言,我可能是真的在内心深处不愿与去承认 Swift 的 ObjC 历史,因为我更愿意把 Swift 当做一门新语言来看待,在这样的背景下,我认为 Swift 是一门完完全全的静态语言,同时,我也认为为了使用 ObjC 运行时,我们需要在 Swift 里继承 NSObject。但是,事实发现这样的看法有问题,Swift 不是一门完完全全的静态语言,Swift 还可以做到动态派发,同时,Swift 可以不用继承 NSObject 而是而实现动态派发(这是苹果的一个彩蛋?查阅Swift文档之后发现,苹果早这么说过,但是我却选择忽略...) 13 | */ 14 | //: ## ObjC Runtime 做了啥 15 | /*: 16 | 我们都知道 ObjC Runtime 做了什么事情,他为我们提供了统一的数据结构,为我们描述了一个类、一个实例具有哪些实实在在的东西,而且,我们也很清楚的知道,ObjC 所有类都必须继承自 NSObject 。 17 | 18 | 更多关于 ObjC Runtime 的东西不在本文的讨论范畴。 19 | */ 20 | //: ## Swift 有运行时吗? 21 | /*: 22 | 答案是肯定的。但是,Swift 没有提供运行时访问的 API ,而且,Swift 本身是静态语言,他的运行时和动态性八竿子打不着。幸运的是,Swift 支持动态化,什么意思呢?Swift 有它自己的运行时,用来处理纯 Swift 语言的,做的工作肯定是很多静态优化的事情,所以,如果需要支持 Swift 动态化,我们需要使用 objc 的运行时机制,而且,Swift 本身就做了对 objc 运行时的兼容。 23 | */ 24 | //: 这是一个 Swift 类: 25 | public class SwiftClass { 26 | public var val: Int 27 | 28 | public init( 29 | _ val: Int) 30 | { 31 | self.val = val 32 | } 33 | 34 | public func echo( 35 | _ val: Int) -> Int 36 | { 37 | return val 38 | } 39 | 40 | @objc 41 | public dynamic func dynamicEcho( 42 | _ val: Int) -> Int 43 | { 44 | return val 45 | } 46 | } 47 | 48 | func objc_classes(of cls: AnyClass) -> [AnyClass] { 49 | var clss: [AnyClass] = [] 50 | var cls: AnyClass? = cls 51 | while let _cls = cls { 52 | clss.append(_cls) 53 | cls = class_getSuperclass(_cls) 54 | } 55 | 56 | return clss 57 | } 58 | //: 创建一个实例: 59 | let classIns = SwiftClass(110) 60 | //: 查看一下 `classIns` 的类型: 61 | print(objc_classes(of: object_getClass(classIns)!)) 62 | // [__lldb_expr_3.SwiftClass, SwiftObject] 63 | //: 可以清楚的看到,`dynamicIns`在 objc 运行时里的根类是 `SwiftObject` 。这很符合我们的预期,因为 Swift 为我们兼容了从 Swift 类到 objc 运行时类的转变。 64 | //: 65 | //: 接下来我们看一下 SwiftObject 类的“料”: 66 | let swiftObjectClass = objc_getClass("SwiftObject".withCString { $0 }) 67 | print(swiftObjectClass as Any) 68 | // SwiftObject 69 | 70 | public func objc_methods(of class: AnyClass) -> [String] { 71 | var count: UInt32 = 0 72 | let methodList = class_copyMethodList(`class`, &count) 73 | return (0.. [String] { 79 | var count: UInt32 = 0 80 | let propertyList = class_copyPropertyList(`class`, &count) 81 | return (0.. [String] { 87 | var count: UInt32 = 0 88 | let ivarList = class_copyIvarList(`class`, &count) 89 | return (0.. [String] { 95 | var count: UInt32 = 0 96 | let protocolList = class_copyProtocolList(`class`, &count) 97 | return (0.. Int 124 | { 125 | return val 126 | } 127 | 128 | // @objc // Error: @objc can only be used with members of classes, @objc protocols, and concrete extensions of classes 129 | // public dynamic func dynamicEcho( // Error: Only members of classes may be dynamic 130 | // _ val: Int) -> Int 131 | // { 132 | // return val 133 | // } 134 | } 135 | 136 | public enum SwiftEnum { 137 | case a, b, c 138 | } 139 | 140 | let structVal = SwiftStruct(val: 110) 141 | let stdIntVal = 110 142 | let stdBoolVal = false 143 | let stdArrVals = ["1", "2", "3"] 144 | let stdDicVals = ["1": 1, "2": 2] 145 | let stdSetVals: Set = [1, 2, 3] 146 | 147 | print(objc_classes(of: object_getClass(structVal)!)) 148 | // [_SwiftValue, NSObject] 149 | print(objc_methods(of: object_getClass(structVal)!)) 150 | // ["isEqual:", "hash", "description", "debugDescription", "dealloc", "copyWithZone:", "_swiftTypeMetadata", "_swiftTypeName", "_swiftValue"] 151 | print(objc_classes(of: object_getClass(SwiftEnum.a)!)) 152 | print(objc_classes(of: object_getClass(stdIntVal)!)) 153 | // [__NSCFNumber, NSNumber, NSValue, NSObject] 154 | print(objc_classes(of: object_getClass(stdBoolVal)!)) 155 | // [__NSCFBoolean, NSNumber, NSValue, NSObject] 156 | print(objc_classes(of: object_getClass(stdArrVals)!)) 157 | // [Swift._SwiftDeferredNSArray, Swift._SwiftNativeNSArrayWithContiguousStorage, Swift._SwiftNativeNSArray, _SwiftNativeNSArrayBase, NSArray, NSObject] 158 | print(objc_classes(of: object_getClass(stdDicVals)!)) 159 | // [Swift._SwiftDeferredNSDictionary, Swift._SwiftNativeNSDictionary, _SwiftNativeNSDictionaryBase, NSDictionary, NSObject] 160 | print(objc_classes(of: object_getClass(stdSetVals)!)) 161 | // [Swift._SwiftDeferredNSSet, Swift._SwiftNativeNSSet, _SwiftNativeNSSetBase, NSSet, NSObject] 162 | //: 可以看出,Swift 的结构体类型在 ObjC 的都有兼容的类型,并且所有的类型的根类型都是 ObjC 的 NSObject 类。我们同样可以对这些类型进行动态派发调用 ObjC 运行时注册的方法,但是对我们自己定义的 Swift 方法就没办法进行动态派发,因为 Struct 结构体类型不支持 `dynamic` . 163 | //: 164 | //: 对于 Swift 可桥接类型,如 Int, Double, Set, Array, Dictionary ,他们在 ObjC 运行时里都有对应的桥类型,比如 Int/Double 对应了 NSNumber 类型,Set 对应了 NSSet 类型,Array 对应了 NSArray,Dictionary 对应了 NSDiction,对于这些类型,我们应该尽量避免桥接使用,因为桥接就没有了 Swift 的静态优化。 165 | //: 166 | //: 而对于 Swift 专有的类型而言,它们在 ObjC 运行时里边类都是 `_SwiftValue`,这个类集成自 NSObject 并且提供了部分 Swift 相关的方法,用来供 Swift 优化使用。 167 | //: ### 为 Swift 类型提供动态派发的能力 168 | //: 我们以结构体举例,新建一个结构体: 169 | public struct DynamicStruct { 170 | public var val: Int 171 | 172 | public func echo( 173 | _ val: Int) -> Int 174 | { 175 | print("Calling echo...") 176 | return val 177 | } 178 | 179 | internal func dispatchEcho( 180 | _ obj: AnyObject, val: Int) -> Int 181 | { 182 | return echo(val) 183 | } 184 | } 185 | //: 生成一个实例: 186 | let dynamicVal = DynamicStruct(val: 110) 187 | //: 为 `dynamicVal` 添加 ObjC 运行时方法: 188 | let block: @convention(block) (AnyObject, Int) -> Int = dynamicVal.dispatchEcho 189 | let imp = imp_implementationWithBlock(unsafeBitCast(block, to: AnyObject.self)) 190 | let dynamicValCls = object_getClass(dynamicVal)! 191 | class_addMethod(dynamicValCls, NSSelectorFromString("objcEcho:"), imp, "@24@0:8@16") 192 | //: 使用 ObjC 动态派发: 193 | print((dynamicVal as AnyObject).perform(NSSelectorFromString("objcEcho:"), with: Int(1000))!.takeRetainedValue()) 194 | //: 结果如我们所期望的一样,为结构体类型添加了 ObjC 动态派发的能力。 195 | // Calling echo... 196 | // 1000 197 | //: ### 我们回到正题 198 | /*: 199 | Swift 有运行时吗?有,所有 Swift 中的类型(结构体、枚举、类)在 ObjC 运行时中都有对应的类来描述,这个一直困惑我的问题总算有了答案。 200 | 201 | ### 做一个总结 202 | Swift 是一门静态语言,所有在 Swift 中声明的方法和属性都是静态编译期就确定了的,同时,Swift 也支持动态绑定和动态派发,只需要将`class`里的属性或方法声明为`@objc` `dynamic`即可,此时,Swift 的动态特性将使用 ObjC Runtime 来实现,完全兼容 ObjC 。 203 | */ 204 | //: ## @objc、@objcMembers、dynamic介绍 205 | /*: 206 | 前面提到,Swift的动态派发依赖 ObjC 的运行时系统,为了将 Swift 的方法属性甚至类型暴露给 ObjC 使用,我们需要声明为 @objc ,此时可以在 ObjC 中访问,但是,声明为 @objc 的属性或方法有可能会被 Swift 优化为静态调用,不一定会动态派发,如果要使用动态特性,需要声明 dynamic ,这样才能完全的使用动态特性。@objcMembers 和 @objc 差不多,区别是: 207 | 208 | - @objcMembers 声明的类会隐式地为所有的属性或方法添加 @objc 标示 209 | - 声明为 @objc 的类需要继承自 NSObject ,而 @objcMembers 不需要继承自 NSObject 210 | 211 | 几个需要注意的点: 212 | 213 | - 在 Swift 3.x 中,继承自 NSObject 的类会隐式地为所有 public 的属性或方法添加 @objc 标示,private 的b属性或方法则需要手动添加 214 | - 在 Swift 4.x 中,继承自 NSObject 的类不会隐式添加 @objc 215 | - @objc 还可以修改暴露给 ObjC 的属性或方法名:`@objc(Name)` 216 | */ 217 | //: ## Swift 的窘境 218 | /*: 219 | Swift 被设计为一门静态语言,却无奈需要兼容历史 Cocoa 库,始终摆脱不掉 ObjC 的包袱,这种情况在未来的几年内应该是 Swift 的常态,这样的一种妥协,为我们提供了 Swift 做动态化的思路。 220 | 221 | 虽然,Swift 兼容了 ObjC 运行时,但是,我们不知道 Swift 这么做的目的是什么,是为了兼容历史的 Cocoa 库?还是说这是 Swift 给我们的彩蛋? 222 | 223 | 也许,这是 Swift 给我们的彩蛋,甚至,未来 Swift 会给我们开放更多动态化的能力。 224 | */ 225 | //: ## AOP 的可能 226 | /*: 227 | 也正是 Swift 兼容了 ObjC 运行时,为我们实现 Swift 平台的 AOP 提供了可能性。AOP 的实现依赖于 intercept pattern ,Swift 支持两种方法调用的方式,静态调用和动态派发,因此,要在 Swift 中实现方法拦截,要么编译器提供支持,否则的话就只有着手于运行时,而 Swift 中所有的类型,都为我们兼容了 ObjC 运行时。 228 | */ 229 | //: [Next](@next) 230 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Strings and Characters.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Strings-and-Characters.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | 10 | //: # 字符与字符串 11 | /*: 12 | 字符串是是一系列字符的集合,例如 "hello, world","albatross"。Swift 的字符串通过 `String` 类型来表示。 一个 `String` 的内容可以用许多方式读取,包括作为一个 `Character` 值的集合。 13 | 14 | `Swift` 的 `String` 和 `Character` 类型提供了快速和兼容 `Unicode` 的方式供你的代码使用。创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。字符串连接操作只需要简单地通过 + 符号将两个字符串相连即可。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你也可以在字符串内插过程中使用字符串插入常量、变量、字面量表达成更长的字符串,这样可以很容易的创建自定义的字符串值,进行展示、存储以及打印。 15 | 16 | 尽管语法简易,但 String 类型是一种快速、现代化的字符串实现。 每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式。 17 | 18 | - Note: 19 | Swift 的 String 类型与 Foundation NSString 类进行了无缝桥接。Foundation 也可以对 String 进行扩展,暴露在 NSString 中定义的方法。 这意味着,如果你在 String 中调用这些 NSString 的方法,将不用进行转换。 20 | 21 | 更多关于在 Foundation 和 Cocoa 中使用 String 的信息请查看 Using Swift with Cocoa and Objective-C (Swift 4)。 22 | */ 23 | 24 | //: ## 字符串字面量 25 | /*: 26 | 你可以在代码里使用一段预定义的字符串值作为字符串字面量。字符串字面量是由一对双引号包裹着的具有固定顺序的字符集。 27 | 28 | 字符串字面量可以用于为常量和变量提供初始值: 29 | 30 | let someString = "Some string literal value" 31 | 32 | 注意 someString 常量通过字符串字面量进行初始化,Swift 会推断该常量为 String 类型。 33 | */ 34 | let someString = "Some string literal value" 35 | //: ## 多行字符串字面量 36 | /*: 37 | 如果你需要一个字符串是跨越多行的,那就使用多行字符串字面量 — 由一对三个双引号包裹着的具有固定顺序的文本字符集: 38 | 39 | let quotation = """ 40 | The White Rabbit put on his spectacles. "Where shall I begin, 41 | please your Majesty?" he asked. 42 | 43 | "Begin at the beginning," the King said gravely, "and go on 44 | till you come to the end; then stop." 45 | """ 46 | 47 | 一个多行字符串字面量包含了所有的在开启和关闭引号(""")中的行。这个字符从开启引号(""")之后的第一行开始,到关闭引号(""")之前为止。这就意味着字符串开启引号之后(""")或者结束引号(""")之前都没有换行符号。(译者:下面两个字符串其实是一样的,虽然第二个使用了多行字符串的形式) 48 | 49 | let singleLineString = "These are the same." 50 | let multilineString = """ 51 | These are the same. 52 | """ 53 | 54 | 如果你的代码中,多行字符串字面量包含换行符的话,则多行字符串字面量中也会包含换行符。如果你想换行,以便加强代码的可读性,但是你又不想在你的多行字符串字面量中出现换行符的话,你可以用在行尾写一个反斜杠(\)作为续行符: 55 | 56 | let softWrappedQuotation = """ 57 | The White Rabbit put on his spectacles. "Where shall I begin, \ 58 | please your Majesty?" he asked. 59 | 60 | "Begin at the beginning," the King said gravely, "and go on \ 61 | till you come to the end; then stop." 62 | """ 63 | 64 | 为了让一个多行字符串字面量开始和结束于换行符,请将换行写在第一行和最后一行,例如: 65 | 66 | let lineBreaks = """ 67 | 68 | This string starts with a line break. 69 | It also ends with a line break. 70 | 71 | """ 72 | 73 | */ 74 | let quotation = """ 75 | The White Rabbit put on his spectacles. "Where shall I begin, 76 | please your Majesty?" he asked. 77 | 78 | "Begin at the beginning," the King said gravely, "and go on 79 | till you come to the end; then stop." 80 | """ 81 | //: ## 字符串字面量的特殊字符 82 | /*: 83 | 字符串字面量可以包含以下特殊字符: 84 | - 转义字符 \0(空字符)、\\(反斜线)、\t(水平制表符)、\n(换行符)、\r(回车符)、\"(双引号)、\'(单引号)。 85 | - Unicode 标量,写成 \u{n}(u 为小写),其中 n 为任意一到八位十六进制数且可用的 Unicode 位码。 86 | 87 | 特俗字符示例: 88 | 89 | let wiseWords = "\"Imagination is more important than knowledge\" - Einstein" 90 | // "Imageination is more important than knowledge" - Enistein 91 | let dollarSign = "\u{24}" // $,Unicode 标量 U+0024 92 | let blackHeart = "\u{2665}" // ♥,Unicode 标量 U+2665 93 | let sparklingHeart = "\u{1F496}" // 💖,Unicode 标量 U+1F496 94 | 95 | 多行字符字面量里可以直接说引号 `"` ,若要使用三引号 `"""` 则需要至少一个转义符: 96 | 97 | let threeDoubleQuotes = """ 98 | Escaping the first quote \""" 99 | Escaping all three quotes \"\"\" 100 | """ 101 | 102 | */ 103 | let wiseWords = "\"Imagination is more important than knowledge\" - Einstein" 104 | let dollarSign = "\u{24}" 105 | let blackHeart = "\u{2665}" 106 | let sparklingHeart = "\u{1F496}" 107 | //: ## 初始化空字符串 108 | /*: 109 | 创建一个空字符串,可以将空的字符串字面量赋值给变量,也可以初始化一个新的 `String` 实例: 110 | 111 | var emptyString = "" // 空字符串字面量 112 | var anotherEmptyString = String() // 初始化方法 113 | // 两个字符串均为空并等价。 114 | 115 | ### 字符串判空 116 | 字符串使用属性 `isEmpty` 进行判空: 117 | 118 | if emptyString.isEmpty { 119 | print("Nothing to see here") 120 | } 121 | // 打印输出:"Nothing to see here" 122 | 123 | */ 124 | var emptyString = "" 125 | var anotherEmptyString = String() 126 | //: ## 字符串可变性 127 | /*: 128 | 与`OC`的可变性不同,`Swift`中 `var` 声明的 `String` 变量是可变字符串,`let` 声明的 `String` 常量是不可变字符串: 129 | 130 | var variableString = "Horse" 131 | variableString += " and carriage" 132 | // variableString 现在为 "Horse and carriage" 133 | 134 | let constantString = "Highlander" 135 | constantString += " and another Highlander" 136 | // 这会报告一个编译错误(compile-time error) - 常量字符串不可以被修改。 137 | 138 | */ 139 | var variableString = "Horse" 140 | variableString += " and carriage" 141 | 142 | let constantString = "Highlander" 143 | // constantString += " and another Highlander" // Error: change 'let' to 'var' to make it mutable 144 | //: ## 字符 145 | /*: 146 | `Swift` 中通过 `for-in` 循环来遍历字符串: 147 | 148 | for character in "Dog!🐶" { 149 | print(character) 150 | } 151 | // D 152 | // o 153 | // g 154 | // ! 155 | // 🐶 156 | 157 | 通过 `Character` 来使用字符;可以通过 `[Character]` 数组初始化字符串: 158 | 159 | let exclamationMark: Character = "!" 160 | 161 | let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"] 162 | let catString = String(catCharacters) 163 | print(catString) 164 | // 打印输出:"Cat!🐱" 165 | */ 166 | 167 | //: ## 字符串拼接 168 | /*: 169 | 字符串可以通过运算符 `+` 、 `+=` 进行拼接;也可以通过方法 `append()` 拼接,`append()` 可以接受 `String` 和 `Character` 类型的实例作为参数: 170 | 171 | var instruction = "look over" 172 | instruction += string2 173 | // instruction 现在等于 "look over there" 174 | 175 | let exclamationMark: Character = "!" 176 | welcome.append(exclamationMark) 177 | // welcome 现在等于 "hello there!" 178 | 179 | */ 180 | var instruction = "look over" 181 | instruction += "wooooow" 182 | 183 | let exclamationMark: Character = "!" 184 | instruction.append(exclamationMark) 185 | //: ## 字符串插值 186 | /*: 187 | 字符串插值可以通过 `\()` 语法将任意值插入到字符串中: 188 | 189 | let multiplier = 3 190 | let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)" 191 | // message 是 "3 times 2.5 is 7.5" 192 | 193 | */ 194 | let multiplier = 3 195 | let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)" 196 | //: ## Unicode 197 | /*: 198 | `Swift` 是基于 `Unicode` 构建的,这意味着,进行字符遍历的时候,我们得到的字符就是一个单元的 `Unicode` 标量: 199 | 200 | for character in "Dog!🐶" { 201 | print(character) 202 | } 203 | // D 204 | // o 205 | // g 206 | // ! 207 | // 🐶 208 | 209 | 如果想遍历其他编码的字符,可以使用 `UTF8View` 等属性进行遍历。 210 | */ 211 | for character in "Dog!🐶,\u{E9}\u{65}\u{301}" { 212 | print(character) 213 | } 214 | //: ## 字符串长度 215 | /*: 216 | 获取字符串长度,可以使用 `count` 属性获取,使用 `count` 获取的长度也是基于 `Unicode` 计算的: 217 | 218 | "Dog!🐶,\u{E9}\u{65}\u{301}".count // 8 219 | 220 | 这样得到的结果可能会与 `NSString` 得到的结果不一样,`NSString` 是通过 `utf16` 编码计算的长度: 221 | 222 | "Dog!🐶,\u{E9}\u{65}\u{301}".utf16.count // 10 223 | ("Dog!🐶,\u{E9}\u{65}\u{301}" as NSString).length // 10 224 | 225 | */ 226 | print("Dog!🐶,\u{E9}\u{65}") 227 | "Dog!🐶,\u{E9}\u{65}".count 228 | "Dog!🐶,\u{E9}\u{65}\u{301}".utf16.count 229 | ("Dog!🐶,\u{E9}\u{65}\u{301}" as NSString).length 230 | //: ## 字符串访问 231 | /*: 232 | `String` 可以通过下标进行访问,根据指定的下标的值,可以获取到对应位置的字符;`Swift` 中的字符串是基于 `Unicode` 构建的,因此,`String` 的下标并不是一个确定的值,需要根据访问的编码类型来确定位移量,可以通过属性 `indices` 来获取字符串的索引范围: 233 | 234 | let greeting = "Guten Tag!" 235 | greeting[greeting.startIndex] 236 | // G 237 | greeting[greeting.index(before: greeting.endIndex)] 238 | // ! 239 | greeting[greeting.index(after: greeting.startIndex)] 240 | // u 241 | let index = greeting.index(greeting.startIndex, offsetBy: 7) 242 | greeting[index] 243 | // a 244 | 245 | for index in greeting.indices { 246 | print("\(greeting[index]) ", terminator: "") 247 | } 248 | // 打印输出 "G u t e n T a g ! " 249 | 250 | **注意:**越界访问将会触发运行时错误! 251 | */ 252 | 253 | //: ## 字符串插入与删除 254 | /*: 255 | 调用 `insert(_:at:)` 方法可以在一个字符串的指定索引插入一个字符,调用 `insert(contentsOf:at:)` 方法可以在一个字符串的指定索引插入一个段字符串: 256 | 257 | var welcome = "hello" 258 | welcome.insert("!", at: welcome.endIndex) 259 | // welcome 变量现在等于 "hello!" 260 | 261 | welcome.insert(contentsOf:" there", at: welcome.index(before: welcome.endIndex)) 262 | // welcome 变量现在等于 "hello there!" 263 | 264 | 调用 `remove(at:)` 方法可以在一个字符串的指定索引删除一个字符,调用 `removeSubrange(_:)` 方法可以在一个字符串的指定索引删除一个子字符串: 265 | 266 | welcome.remove(at: welcome.index(before: welcome.endIndex)) 267 | // welcome 现在等于 "hello there" 268 | 269 | let range = welcome.index(welcome.endIndex, offsetBy: -6)..(_ a: inout T, _ b: inout T) { 39 | let temporaryA = a 40 | a = b 41 | b = temporaryA 42 | } 43 | 44 | var someInt = 3 45 | var anotherInt = 107 46 | swapTwoValues(&someInt, &anotherInt) 47 | // someInt 现在 107, and anotherInt 现在 3 48 | 49 | var someString = "hello" 50 | var anotherString = "world" 51 | swapTwoValues(&someString, &anotherString) 52 | // someString 现在 "world", and anotherString 现在 "hello" 53 | 54 | */ 55 | 56 | //: ## 类型参数 57 | /*: 58 | 在上面的 swapTwoValues(_:_:) 例子中,占位类型 T 是**类型参数**的一个例子。类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 )。 59 | 60 | 一旦一个类型参数被指定,就可以用它来定义一个函数的参数类型(例如 swapTwoValues(_:_:) 函数中的参数 a 和 b),或者作为函数的返回类型,还可以用作函数体变量类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。 61 | 62 | 可以提供多个类型参数,将它们都写在尖括号中,用逗号分开。 63 | */ 64 | 65 | //: ## 泛型类型 66 | /*: 67 | Swift 还可以定义泛型类型。泛型类、结构体和枚举可以适用于任何类型,类似于 Array 和 Dictionary。 68 | 69 | 定义一个 Stack : 70 | 71 | struct IntStack { 72 | var items = [Int]() 73 | mutating func push(_ item: Int) { 74 | items.append(item) 75 | } 76 | mutating func pop() -> Int { 77 | return items.removeLast() 78 | } 79 | } 80 | 81 | 泛型版本: 82 | 83 | struct Stack { 84 | var items = [Element]() 85 | mutating func push(_ item: Element) { 86 | items.append(item) 87 | } 88 | mutating func pop() -> Element { 89 | return items.removeLast() 90 | } 91 | } 92 | */ 93 | 94 | //: ## 泛型类型的扩展 95 | /*: 96 | 当扩展一个泛型类型的时候,并不需要在扩展的定义中提供*类型参数(列表。类型定义中声明的类型参数列表在扩展中可以直接使用: 97 | 98 | extension Stack { 99 | var topItem: Element? { 100 | return items.isEmpty ? nil : items[items.count - 1] 101 | } 102 | } 103 | 104 | if let topItem = stackOfStrings.topItem { 105 | print("The top item on the stack is \(topItem).") 106 | } 107 | // 打印 “The top item on the stack is tres.” 108 | 109 | */ 110 | 111 | //: ## 类型约束 112 | /*: 113 | 类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。 114 | 115 | 可以在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分。对泛型函数添加类型约束的基本语法如下所示(作用于泛型类型时的语法与之相同): 116 | 117 | func someFunction(someT: T, someU: U) { 118 | // 这里是泛型函数的函数体部分 119 | } 120 | 121 | 示例: 122 | 123 | func findIndex(ofString valueToFind: String, in array: [String]) -> Int? { 124 | for (index, value) in array.enumerated() { 125 | if value == valueToFind { 126 | return index 127 | } 128 | } 129 | return nil 130 | } 131 | 132 | let strings = ["cat", "dog", "llama", "parakeet", "terrapin"] 133 | if let foundIndex = findIndex(ofString: "llama", in: strings) { 134 | print("The index of llama is \(foundIndex)") 135 | } 136 | // 打印 “The index of llama is 2” 137 | 138 | 使用类型约束的泛型版本: 139 | 140 | func findIndex(of valueToFind: T, in array:[T]) -> Int? { 141 | for (index, value) in array.enumerated() { 142 | if value == valueToFind { 143 | return index 144 | } 145 | } 146 | return nil 147 | } 148 | 149 | let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25]) 150 | // doubleIndex 类型为 Int?,其值为 nil,因为 9.3 不在数组中 151 | let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"]) 152 | // stringIndex 类型为 Int?,其值为 2 153 | 154 | */ 155 | 156 | //: ## 关联类型 157 | /*: 158 | 关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。可以通过 associatedtype 关键字来指定关联类型。 159 | 160 | 示例: 161 | 162 | protocol Container { 163 | associatedtype Item 164 | mutating func append(_ item: Item) 165 | var count: Int { get } 166 | subscript(i: Int) -> Item { get } 167 | } 168 | 169 | 实现协议: 170 | 171 | struct IntStack: Container { 172 | // IntStack 的原始实现部分 173 | var items = [Int]() 174 | mutating func push(_ item: Int) { 175 | items.append(item) 176 | } 177 | mutating func pop() -> Int { 178 | return items.removeLast() 179 | } 180 | // Container 协议的实现部分 181 | typealias Item = Int 182 | mutating func append(_ item: Int) { 183 | self.push(item) 184 | } 185 | var count: Int { 186 | return items.count 187 | } 188 | subscript(i: Int) -> Int { 189 | return items[i] 190 | } 191 | } 192 | 193 | 也可以定义泛型类型实现泛型协议: 194 | 195 | struct Stack: Container { 196 | // Stack 的原始实现部分 197 | var items = [Element]() 198 | mutating func push(_ item: Element) { 199 | items.append(item) 200 | } 201 | mutating func pop() -> Element { 202 | return items.removeLast() 203 | } 204 | // Container 协议的实现部分 205 | mutating func append(_ item: Element) { 206 | self.push(item) 207 | } 208 | var count: Int { 209 | return items.count 210 | } 211 | subscript(i: Int) -> Element { 212 | return items[i] 213 | } 214 | } 215 | 216 | 扩展已有类型实现泛型协议: 217 | 218 | extension Array: Container {} 219 | 220 | */ 221 | 222 | //: ## 关联类型约束 223 | /*: 224 | 可以给协议里的关联类型添加类型注释,让遵守协议的类型必须遵循这个约束条件: 225 | 226 | protocol Container { 227 | associatedtype Item: Equatable 228 | mutating func append(_ item: Item) 229 | var count: Int { get } 230 | subscript(i: Int) -> Item { get } 231 | } 232 | 233 | ### 关联类型约束里使用协议 234 | 协议可以作为它自身的定义出现: 235 | 236 | protocol SuffixableContainer: Container { 237 | associatedtype Suffix: SuffixableContainer where Suffix.Item == Item 238 | func suffix(_ size: Int) -> Suffix 239 | } 240 | 241 | extension Stack: SuffixableContainer { 242 | func suffix(_ size: Int) -> Stack { 243 | var result = Stack() 244 | for index in (count-size)..() 252 | stackOfInts.append(10) 253 | stackOfInts.append(20) 254 | stackOfInts.append(30) 255 | let suffix = stackOfInts.suffix(2) 256 | // suffix contains 20 and 30 257 | 258 | extension IntStack: SuffixableContainer { 259 | func suffix(_ size: Int) -> Stack { 260 | var result = Stack() 261 | for index in (count-size)... 267 | } 268 | 269 | */ 270 | 271 | //: ## 泛型 Where 语句 272 | /*: 273 | 类型约束能够为泛型函数,下标,类型的类型参数定义一些强制要求。关联类型定义约束可以在参数列表中通过 where 子句为关联类型定义约束。通过 where 子句要求一个关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。通过将 where 关键字紧跟在类型参数列表后面来定义 where 子句,where 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。可以在函数体或者类型的大括号之前添加 where 子句: 274 | 275 | func allItemsMatch 276 | (_ someContainer: C1, _ anotherContainer: C2) -> Bool 277 | where C1.Item == C2.Item, C1.Item: Equatable { 278 | 279 | // 检查两个容器含有相同数量的元素 280 | if someContainer.count != anotherContainer.count { 281 | return false 282 | } 283 | 284 | // 检查每一对元素是否相等 285 | for i in 0..() 296 | stackOfStrings.push("uno") 297 | stackOfStrings.push("dos") 298 | stackOfStrings.push("tres") 299 | 300 | var arrayOfStrings = ["uno", "dos", "tres"] 301 | 302 | if allItemsMatch(stackOfStrings, arrayOfStrings) { 303 | print("All items match.") 304 | } else { 305 | print("Not all items match.") 306 | } 307 | // 打印 “All items match.” 308 | */ 309 | 310 | //: ## 扩展中的 Where 语句 311 | /*: 312 | 可以使用泛型 where 子句作为扩展的一部分: 313 | 314 | extension Stack where Element: Equatable { 315 | func isTop(_ item: Element) -> Bool { 316 | guard let topItem = items.last else { 317 | return false 318 | } 319 | return topItem == item 320 | } 321 | } 322 | 323 | if stackOfStrings.isTop("tres") { 324 | print("Top element is tres.") 325 | } else { 326 | print("Top element is something else.") 327 | } 328 | // 打印 "Top element is tres." 329 | 330 | 可以使用泛型 where 子句去扩展一个协议: 331 | 332 | extension Container where Item: Equatable { 333 | func startsWith(_ item: Item) -> Bool { 334 | return count >= 1 && self[0] == item 335 | } 336 | } 337 | 338 | if [9, 9, 9].startsWith(42) { 339 | print("Starts with 42.") 340 | } else { 341 | print("Starts with something else.") 342 | } 343 | // 打印 "Starts with something else." 344 | */ 345 | 346 | //: ## 关联类型的 Where 语句 347 | /*: 348 | 可以在关联类型后面加上具有泛型 where 的字句: 349 | 350 | protocol Container { 351 | associatedtype Item 352 | mutating func append(_ item: Item) 353 | var count: Int { get } 354 | subscript(i: Int) -> Item { get } 355 | 356 | associatedtype Iterator: IteratorProtocol where Iterator.Element == Item 357 | func makeIterator() -> Iterator 358 | } 359 | 360 | 一个协议继承另一个协议,通过在协议声明的时候,包含泛型 where 子句,来添加了一个约束到被继承协议的关联类型: 361 | 362 | protocol ComparableContainer: Container where Item: Comparable { } 363 | 364 | */ 365 | 366 | //: ## 泛型下标 367 | /*: 368 | 下标可以是泛型的,他们能够包含泛型 where 子句。可以把占位符类型的名称写在 subscript 后面的尖括号里,在下标代码体开始的标志的花括号之前写下泛型 where 子句。例如: 369 | 370 | extension Container { 371 | subscript(indices: Indices) -> [Item] 372 | where Indices.Iterator.Element == Int { 373 | var result = [Item]() 374 | for index in indices { 375 | result.append(self[index]) 376 | } 377 | return result 378 | } 379 | } 380 | 381 | */ 382 | //: [下一页](@next) 383 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Access Control.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Access-Control.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 访问控制 10 | /*: 11 | 访问控制可以限定其它源文件或模块中的代码对我们代码的访问级别。这个特性可以让我们隐藏代码的一些实现细节,并且可以为其他人可以访问和使用的代码提供接口。 12 | 13 | 可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、方法、构造器、下标等设置访问级别。协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。 14 | 15 | Swift 不仅提供了多种不同的访问级别,还为某些典型场景提供了默认的访问级别,这样就不需要我们在每段代码中都申明显式访问级别。 16 | */ 17 | //: ## 模块和源文件 18 | /*: 19 | Swift 中的访问控制模型基于模块和源文件这两个概念。 20 | 21 | 模块指的是独立的代码单元,框架或应用程序会作为一个独立的模块来构建和发布。在 Swift 中,一个模块可以使用 import 关键字导入另外一个模块。 22 | 23 | 在 Swift 中,Xcode 的每个 target(例如框架或应用程序)都被当作独立的模块处理。如果为了实现某个通用的功能,或者是为了封装一些常用方法而将代码打包成独立的框架,这个框架就是 Swift 中的一个模块。当它被导入到某个应用程序或者其他框架时,框架内容都将属于这个独立的模块。 24 | 25 | 源文件就是 Swift 中的源代码文件,它通常属于一个模块,即一个应用程序或者框架。尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。 26 | */ 27 | //: ## 访问级别 28 | /*: 29 | Swift 为代码中的实体提供了五种不同的访问级别。这些访问级别不仅与源文件中定义的实体相关,同时也与源文件所属的模块相关。 30 | 31 | - Open 和 Public 级别可以让实体被同一模块源文件中的所有实体访问,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,你会使用 Open 或 Public 级别来指定框架的外部接口。Open 和 Public 的区别在后面会提到。 32 | - Internal 级别让实体被同一模块源文件中的任何实体访问,但是不能被模块外的实体访问。通常情况下,如果某个接口只在应用程序或框架内部使用,就可以将其设置为 Internal 级别。 33 | - File-private 限制实体只能在其定义的文件内部访问。如果功能的部分细节只需要在文件内使用时,可以使用 File-private 来将其隐藏。 34 | - Private 限制实体只能在其定义的作用域,以及同一文件内的 extension 访问。如果功能的部分细节只需要在当前作用域内使用时,可以使用 Private 来将其隐藏。 35 | 36 | Open 为最高访问级别(限制最少),Private 为最低访问级别(限制最多)。 37 | 38 | Open 只能作用于类和类的成员,它和 Public 的区别如下: 39 | - Public 或者其它更严访问级别的类,只能在其定义的模块内部被继承。 40 | - Public 或者其它更严访问级别的类成员,只能在其定义的模块内部的子类中重写。 41 | - Open 的类,可以在其定义的模块中被继承,也可以在引用它的模块中被继承。 42 | - Open 的类成员,可以在其定义的模块中子类中重写,也可以在引用它的模块中的子类重写。 43 | 44 | 把一个类标记为 open,明确的表示你已经充分考虑过外部模块使用此类作为父类的影响,并且设计好了你的类的代码了。 45 | */ 46 | //: ## 访问级别基本原则 47 | /*: 48 | Swift 中的访问级别遵循一个基本原则:不可以在某个实体中定义访问级别更低(更严格)的实体。 49 | 50 | 例如: 51 | 52 | - 一个 Public 的变量,其类型的访问级别不能是 Internal,File-private 或是 Private。因为无法保证变量的类型在使用变量的地方也具有访问权限。 53 | - 函数的访问级别不能高于它的参数类型和返回类型的访问级别。因为这样就会出现函数可以在任何地方被访问,但是它的参数类型和返回类型却不可以的情况。 54 | */ 55 | //: ## 默认访问级别 56 | /*: 57 | 如果你没有为代码中的实体显式指定访问级别,那么它们默认为 internal 级别(有一些例外情况,稍后会进行说明)。因此,在大多数情况下,我们不需要显式指定实体的访问级别。 58 | */ 59 | //: ## 单 target 应用程序的访问级别 60 | /*: 61 | 当编写一个单目标应用程序时,应用的所有功能都是为该应用服务,而不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别 Internal 即可。但是,你也可以使用 fileprivate 访问或 private 访问级别,用于隐藏一些功能的实现细节。 62 | */ 63 | //: ## 框架的访问级别 64 | /*: 65 | 当开发框架时,就需要把一些对外的接口定义为 Open 或 Public,以便使用者导入该框架后可以正常使用其功能。这些被你定义为对外的接口,就是这个框架的 API。 66 | 67 | - Note: 68 | 框架依然会使用默认的 internal ,也可以指定为 fileprivate 访问或者 private 访问级别。当你想把某个实体作为框架的 API 的时候,需显式为其指定开放访问或公开访问级别。 69 | */ 70 | //: ## 单元测试 target 的访问级别 71 | /*: 72 | 当应用程序包含单元测试 target 时,为了测试,测试模块需要访问应用程序模块中的代码。默认情况下只有 open 或 public 级别的实体才可以被其他模块访问。然而,如果在导入应用程序模块的语句前使用 @testable 特性,然后在允许测试的编译设置(Build Options -> Enable Testability)下编译这个应用程序模块,单元测试目标就可以访问应用程序模块中所有内部级别的实体。 73 | */ 74 | //: ## 访问控制语法 75 | /*: 76 | 通过修饰符 open,public,internal,fileprivate,private 来声明实体的访问级别: 77 | 78 | public class SomePublicClass {} 79 | internal class SomeInternalClass {} 80 | fileprivate class SomeFilePrivateClass {} 81 | private class SomePrivateClass {} 82 | 83 | public var somePublicVariable = 0 84 | internal let someInternalConstant = 0 85 | fileprivate func someFilePrivateFunction() {} 86 | private func somePrivateFunction() {} 87 | 88 | 除非专门指定,否则实体默认的访问级别为 internal。这意味着在不使用修饰符显式声明访问级别的情况下,SomeInternalClass 和 someInternalConstant 仍然拥有隐式的 internal: 89 | 90 | class SomeInternalClass {} // 隐式 internal 91 | var someInternalConstant = 0 // 隐式 internal 92 | 93 | */ 94 | //: ## 自定义类型 95 | /*: 96 | 如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可。新类型只能在它的访问级别限制范围内使用。例如,你定义了一个 fileprivate 级别的类,那这个类就只能在定义它的源文件中使用,可以作为属性类型、函数参数类型或者返回类型,等等。 97 | 98 | 一个类型的访问级别也会影响到类型成员(属性、方法、构造器、下标)的默认访问级别。如果将类型指定为 private 或者 fileprivate 级别,那么该类型的所有成员的默认访问级别也会变成 private 或者 fileprivate 级别。如果将类型指定为公开或者 internal (或者不明确指定访问级别,而使用默认的 internal ),那么该类型的所有成员的默认访问级别将是内部访问。 99 | 100 | - Note: 101 | 上面提到,一个 public 类型的所有成员的访问级别默认为 internal 级别,而不是 public 级别。如果你想将某个成员指定为 public 级别,那么你必须显式指定。这样做的好处是,在你定义公共接口的时候,可以明确地选择哪些接口是需要公开的,哪些是内部使用的,避免不小心将内部使用的接口公开。 102 | 103 | public class SomePublicClass { // 显式 public 类 104 | public var somePublicProperty = 0 // 显式 public 类成员 105 | var someInternalProperty = 0 // 隐式 internal 类成员 106 | fileprivate func someFilePrivateMethod() {} // 显式 fileprivate 类成员 107 | private func somePrivateMethod() {} // 显式 private 类成员 108 | } 109 | 110 | class SomeInternalClass { // 隐式 internal 类 111 | var someInternalProperty = 0 // 隐式 internal 类成员 112 | fileprivate func someFilePrivateMethod() {} // 显式 fileprivate 类成员 113 | private func somePrivateMethod() {} // 显式 private 类成员 114 | } 115 | 116 | fileprivate class SomeFilePrivateClass { // 显式 fileprivate 类 117 | func someFilePrivateMethod() {} // 隐式 fileprivate 类成员 118 | private func somePrivateMethod() {} // 显式 private 类成员 119 | } 120 | 121 | private class SomePrivateClass { // 显式 private 类 122 | func somePrivateMethod() {} // 隐式 private 类成员 123 | } 124 | 125 | */ 126 | //: ## 元组类型 127 | /*: 128 | 元组的访问级别将由元组中访问级别最严格的类型来决定。例如,如果你构建了一个包含两种不同类型的元组,其中一个类型为 `internal`,另一个类型为 `private`,那么这个元组的访问级别为 `private`。 129 | 130 | - Note: 131 | 元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推断出的,而无法明确指定。 132 | */ 133 | //: ## 函数类型 134 | /*: 135 | 函数的访问级别根据访问级别最严格的参数类型或返回类型的访问级别来决定。但是,如果这种访问级别不符合函数定义所在环境的默认访问级别,那么就需要明确地指定该函数的访问级别。 136 | 137 | 下面的例子定义了一个名为 `someFunction()` 的全局函数,并且没有明确地指定其访问级别。也许你会认为该函数应该拥有默认的访问级别 `internal`,但事实并非如此。事实上,如果按下面这种写法,代码将无法通过编译: 138 | 139 | func someFunction() -> (SomeInternalClass, SomePrivateClass) { 140 | // 此处是函数实现部分 141 | } 142 | 143 | */ 144 | //: ## 枚举类型 145 | /*: 146 | 枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不同的访问级别。 147 | 148 | 比如下面的例子,枚举 CompassPoint 被明确指定为 public,那么它的成员 North、South、East、West 的访问级别同样也是 public: 149 | 150 | public enum CompassPoint { 151 | case North 152 | case South 153 | case East 154 | case West 155 | } 156 | 157 | ### 原始值和关联值 158 | 159 | 枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。例如,你不能在一个 internal 的枚举中定义 private 的原始值类型。 160 | */ 161 | //: ## 嵌套类型 162 | /*: 163 | 如果在 private 的类型中定义嵌套类型,那么该嵌套类型就自动拥有 private 访问级别。如果在 public 或者 internal 级别的类型中定义嵌套类型,那么该嵌套类型自动拥有 internal 访问级别。如果想让嵌套类型拥有 public 访问级别,那么需要明确指定该嵌套类型的访问级别。 164 | */ 165 | //: ## 子类 166 | /*: 167 | 子类的访问级别不得高于父类的访问级别。例如,父类的访问级别是 internal,子类的访问级别就不能是 public。 168 | 169 | 此外,可以在符合当前访问级别的条件下重写任意类成员(方法、属性、构造器、下标等)。 170 | 171 | 可以通过重写为继承来的类成员提供更高的访问级别。下面的例子中,类 A 的访问级别是 public,它包含一个方法 someMethod(),访问级别为 private。类 B 继承自类 A,访问级别为 internal,但是在类 B 中重写了类 A 中访问级别为 private 的方法 someMethod(),并重新指定为 internal 级别。通过这种方式,我们就可以将某类中 private 级别的类成员重新指定为更高的访问级别,以便其他人使用: 172 | 173 | public class A { 174 | private func someMethod() {} 175 | } 176 | 177 | internal class B: A { 178 | override internal func someMethod() {} 179 | } 180 | 181 | 我们甚至可以在子类中,用子类成员去访问访问级别更低的父类成员,只要这一操作在相应访问级别的限制范围内(也就是说,在同一源文件中访问父类 private 级别的成员,在同一模块内访问父类 internal 级别的成员): 182 | 183 | public class A { 184 | private func someMethod() {} 185 | } 186 | 187 | internal class B: A { 188 | override internal func someMethod() { 189 | super.someMethod() 190 | } 191 | } 192 | 193 | 因为父类 A 和子类 B 定义在同一个源文件中,所以在子类 B 可以在重写的 someMethod() 方法中调用 super.someMethod()。 194 | */ 195 | //: ## 常量、变量、属性、下标 196 | /*: 197 | 常量、变量、属性不能拥有比它们的类型更高的访问级别。例如,你不能定义一个 public 级别的属性,但是它的类型却是 private 级别的。同样,下标也不能拥有比索引类型或返回类型更高的访问级别。 198 | 199 | 如果常量、变量、属性、下标的类型是 private 级别的,那么它们必须明确指定访问级别为 private: 200 | 201 | private var privateInstance = SomePrivateClass() 202 | 203 | */ 204 | //: ## Getter 和 Setter 205 | /*: 206 | 常量、变量、属性、下标的 Getters 和 Setters 的访问级别和它们所属类型的访问级别相同。 207 | 208 | Setter 的访问级别可以低于对应的 Getter 的访问级别,这样就可以控制变量、属性或下标的读写权限。在 var 或 subscript 关键字之前,你可以通过 fileprivate(set),private(set) 或 internal(set) 为它们的写入权限指定更低的访问级别。 209 | 210 | - Note: 211 | 这个规则同时适用于存储型属性和计算型属性。即使你不明确指定存储型属性的 Getter 和 Setter,Swift 也会隐式地为其创建 Getter 和 Setter,用于访问该属性的后备存储。使用 fileprivate(set),private(set) 和 internal(set) 可以改变 Setter 的访问级别,这对计算型属性也同样适用。 212 | 213 | 示例: 214 | 215 | struct TrackedString { 216 | private(set) var numberOfEdits = 0 217 | var value: String = "" { 218 | didSet { 219 | numberOfEdits += 1 220 | } 221 | } 222 | } 223 | 224 | var stringToEdit = TrackedString() 225 | stringToEdit.value = "This string will be tracked." 226 | stringToEdit.value += " This edit will increment numberOfEdits." 227 | stringToEdit.value += " So will this one." 228 | print("The number of edits is \(stringToEdit.numberOfEdits)") 229 | // 打印 “The number of edits is 3” 230 | 231 | public struct TrackedString { 232 | public private(set) var numberOfEdits = 0 233 | public var value: String = "" { 234 | didSet { 235 | numberOfEdits += 1 236 | } 237 | } 238 | public init() {} 239 | } 240 | */ 241 | //: ## 构造器 242 | /*: 243 | 自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是必要构造器,它的访问级别必须和所属类型的访问级别相同。 244 | 245 | 如同函数或方法的参数,构造器参数的访问级别也不能低于构造器本身的访问级别。 246 | */ 247 | //: ## 默认构造器 248 | /*: 249 | 默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是 public。如果一个类型被指定为 public 级别,那么默认构造器的访问级别将为 internal。如果你希望一个 public 级别的类型也能在其他模块中使用这种无参数的默认构造器,你只能自己提供一个 public 访问级别的无参数构造器。 250 | */ 251 | //: ## 结构体默认的成员逐一构造器 252 | /*: 253 | 如果结构体中任意存储型属性的访问级别为 private,那么该结构体默认的成员逐一构造器的访问级别就是 private。否则,这种构造器的访问级别依然是 internal。 254 | 255 | 如同前面提到的默认构造器,如果你希望一个 public 级别的结构体也能在其他模块中使用其默认的成员逐一构造器,你依然只能自己提供一个 public 访问级别的成员逐一构造器。 256 | */ 257 | //: ## 协议 258 | /*: 259 | 如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被采纳。 260 | 261 | 协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意采纳者都将可用。 262 | 263 | - Note: 264 | 如果你定义了一个 public 访问级别的协议,那么该协议的所有实现也会是 public 访问级别。这一点不同于其他类型,例如,当类型是 public 访问级别时,其成员的访问级别却只是 internal。 265 | */ 266 | //: ## 协议继承 267 | /*: 268 | 如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同。例如,你不能将继承自 internal 协议的新协议定义为 public 协议。 269 | */ 270 | //: ## 协议一致性 271 | /*: 272 | 一个类型可以采纳比自身访问级别低的协议。例如,你可以定义一个 public 级别的类型,它可以在其他模块中使用,同时它也可以采纳一个 internal 级别的协议,但是只能在该协议所在的模块中作为符合该协议的类型使用。 273 | 274 | 采纳了协议的类型的访问级别取它本身和所采纳协议两者间最低的访问级别。也就是说如果一个类型是 public 级别,采纳的协议是 internal 级别,那么采纳了这个协议后,该类型作为符合协议的类型时,其访问级别也是 internal。 275 | 276 | 如果你采纳了协议,那么实现了协议的所有要求后,你必须确保这些实现的访问级别不能低于协议的访问级别。例如,一个 public 级别的类型,采纳了 internal 级别的协议,那么协议的实现至少也得是 internal 级别。 277 | 278 | - Note: 279 | Swift 和 Objective-C 一样,协议的一致性是全局的,也就是说,在同一程序中,一个类型不可能用两种不同的方式实现同一个协议。 280 | */ 281 | //: ## 扩展 282 | /*: 283 | Extension 可以在访问级别允许的情况下对类、结构体、枚举进行扩展。Extension 的成员具有和原始类型成员一致的访问级别。例如,你使用 extension 扩展了一个 public 或者 internal 类型,extension 中的成员就默认使用 internal 访问级别,和原始类型中的成员一致。如果你使用 extension 扩展了一个 private 类型,则 extension 的成员默认使用 private 访问级别。 284 | 285 | 或者,你可以明确指定 extension 的访问级别(例如,private extension),从而给该 extension 中的所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独指定的访问级别所覆盖。 286 | 287 | 如果你使用 extension 来遵循协议的话,就不能显式地声明 extension 的访问级别。extension 每个 protocol 要求的实现都默认使用 protocol 的访问级别。 288 | */ 289 | //: ## 扩展的私有成员 290 | /*: 291 | 扩展同一文件内的类,结构体或者枚举,extension 里的代码会表现得跟声明在原类型里的一模一样。也就是说你可以这样: 292 | - 在类型的声明里声明一个私有成员,在同一文件的 extension 里访问。 293 | - 在 extension 里声明一个私有成员,在同一文件的另一个 extension 里访问。 294 | - 在 extension 里声明一个私有成员,在同一文件的类型声明里访问。 295 | 296 | 这意味着可以像组织的代码去使用 extension,而且不受私有成员的影响。例如,给定下面这样一个简单的协议: 297 | 298 | protocol SomeProtocol { 299 | func doSomething() {} 300 | } 301 | 302 | 你可以使用 extension 来遵守协议: 303 | 304 | struct SomeStruct { 305 | private var privateVariable = 12 306 | } 307 | 308 | extension SomeStruct: SomeProtocol { 309 | func doSomething() { 310 | print(privateVariable) 311 | } 312 | } 313 | 314 | */ 315 | //: ## 泛型 316 | /*: 317 | 泛型类型或泛型函数的访问级别取决于泛型类型或泛型函数本身的访问级别,还需结合类型参数的类型约束的访问级别,根据这些访问级别中的最低访问级别来确定。 318 | */ 319 | //: ## 类型别名 320 | /*: 321 | 定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。例如,private 级别的类型别名可以作为 private、file-private、internal、public 或者 open 类型的别名,但是 public 级别的类型别名只能作为 public 类型的别名,不能作为 internal、file-private 或 private 类型的别名。 322 | */ 323 | //: [下一页](@next) 324 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Protocols.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Protocols.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # 协议 10 | /*: 11 | **协议**定义了一系列用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以遵循协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型遵循这个协议。 12 | 13 | 除了遵循协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样遵循协议的类型就能够使用这些功能。 14 | */ 15 | 16 | //: ## 协议语法 17 | /*: 18 | 协议的定义方式与类、结构体和枚举的定义非常相似: 19 | 20 | protocol SomeProtocol { 21 | // 这里是协议的定义部分 22 | } 23 | 24 | 让自定义类型遵循某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(:)分隔。遵循多个协议时,各协议之间用逗号(,)分隔: 25 | 26 | struct SomeStructure: FirstProtocol, AnotherProtocol { 27 | // 这里是结构体的定义部分 28 | } 29 | 30 | 拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔: 31 | 32 | class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol { 33 | // 这里是类的定义部分 34 | } 35 | 36 | */ 37 | 38 | //: ## 属性 39 | /*: 40 | 协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。 41 | 42 | 如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。 43 | 44 | 协议总是用 var 关键字来声明变量属性,在类型声明后加上 { set get } 来表示属性是可读可写的,可读属性则用 { get } 来表示: 45 | 46 | protocol SomeProtocol { 47 | var mustBeSettable: Int { get set } 48 | var doesNotNeedToBeSettable: Int { get } 49 | } 50 | 51 | 在协议中定义类型属性时,使用 static 关键字作为前缀即可。当类类型遵循协议时,除了 static 关键字,还可以使用 class 关键字来声明类型属性: 52 | 53 | protocol AnotherProtocol { 54 | static var someTypeProperty: Int { get set } 55 | } 56 | 57 | 示例: 58 | 59 | protocol FullyNamed { 60 | var fullName: String { get } 61 | } 62 | 63 | 实现协议: 64 | 65 | struct Person: FullyNamed { 66 | var fullName: String 67 | } 68 | let john = Person(fullName: "John Appleseed") 69 | // john.fullName 为 "John Appleseed" 70 | 71 | class Starship: FullyNamed { 72 | var prefix: String? 73 | var name: String 74 | init(name: String, prefix: String? = nil) { 75 | self.name = name 76 | self.prefix = prefix 77 | } 78 | var fullName: String { 79 | return (prefix != nil ? prefix! + " " : "") + name 80 | } 81 | } 82 | var ncc1701 = Starship(name: "Enterprise", prefix: "USS") 83 | // ncc1701.fullName 是 "USS Enterprise" 84 | 85 | */ 86 | 87 | //: ## 方法 88 | /*: 89 | 协议可以声明实例方法和类方法。定义函数的时候只需要声明而不需要实现。可以定义可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法的参数提供默认值。 90 | 91 | 在协议中定义类方法的时候,使用 static 关键字作为前缀。当类类型遵循协议时,除了 static 关键字,还可以使用 class 关键字作为前缀: 92 | 93 | protocol SomeProtocol { 94 | static func someTypeMethod() 95 | } 96 | 97 | 示例: 98 | 99 | protocol RandomNumberGenerator { 100 | func random() -> Double 101 | } 102 | 103 | protocol Togglable { 104 | mutating func toggle() 105 | } 106 | 107 | 实现协议: 108 | 109 | class LinearCongruentialGenerator: RandomNumberGenerator { 110 | var lastRandom = 42.0 111 | let m = 139968.0 112 | let a = 3877.0 113 | let c = 29573.0 114 | func random() -> Double { 115 | lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m)) 116 | return lastRandom / m 117 | } 118 | } 119 | let generator = LinearCongruentialGenerator() 120 | print("Here's a random number: \(generator.random())") 121 | // 打印 “Here's a random number: 0.37464991998171” 122 | print("And another one: \(generator.random())") 123 | // 打印 “And another one: 0.729023776863283” 124 | 125 | enum OnOffSwitch: Togglable { 126 | case off, on 127 | mutating func toggle() { 128 | switch self { 129 | case .off: 130 | self = .on 131 | case .on: 132 | self = .off 133 | } 134 | } 135 | } 136 | var lightSwitch = OnOffSwitch.off 137 | lightSwitch.toggle() 138 | // lightSwitch 现在的值为 .on 139 | */ 140 | 141 | //: ## 构造器 142 | /*: 143 | 协议可以要求遵循协议的类型实现指定的构造器: 144 | 145 | protocol SomeProtocol { 146 | init(someParameter: Int) 147 | } 148 | 149 | ### 使用类实现构造器 150 | 可以在遵循协议的类中实现构造器,无论是指定构造器,还是便利构造器,都必须为构造器实现标上 required 修饰符: 151 | 152 | class SomeClass: SomeProtocol { 153 | required init(someParameter: Int) { 154 | // 这里是构造器的实现部分 155 | } 156 | } 157 | 158 | - Note: 159 | 如果类已经被标记为 final,那么不需要在协议构造器的实现中使用 required 修饰符,因为 final 类不能有子类。 160 | 如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 required 和 override 修饰符: 161 | protocol SomeProtocol { 162 | init() 163 | } 164 | 165 | class SomeSuperClass { 166 | init() { 167 | // 这里是构造器的实现部分 168 | } 169 | } 170 | 171 | class SomeSubClass: SomeSuperClass, SomeProtocol { 172 | // 因为遵循协议,需要加上 required 173 | // 因为继承自父类,需要加上 override 174 | required override init() { 175 | // 这里是构造器的实现部分 176 | } 177 | } 178 | 179 | */ 180 | 181 | //: # 可失败构造器 182 | /*: 183 | 协议还可以为遵循协议的类型定义可失败构造器: 184 | 185 | 遵循协议的类型可以通过可失败构造器(init?)或非可失败构造器(init)来实现协议中定义的可失败构造器。协议中定义的非可失败构造器可以通过非可失败构造器(init)或隐式解包可失败构造器(init!)来实现 186 | */ 187 | 188 | //: ## 协议作为类型 189 | /*: 190 | 协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用。 191 | 192 | 协议可以像其他普通类型一样使用,使用场景如下: 193 | - 作为函数、方法或构造器中的参数类型或返回值类型 194 | - 作为常量、变量或属性的类型 195 | - 作为数组、字典或其他容器中的元素类型 196 | 197 | 示例: 198 | 199 | class Dice { 200 | let sides: Int 201 | let generator: RandomNumberGenerator 202 | init(sides: Int, generator: RandomNumberGenerator) { 203 | self.sides = sides 204 | self.generator = generator 205 | } 206 | func roll() -> Int { 207 | return Int(generator.random() * Double(sides)) + 1 208 | } 209 | } 210 | 211 | var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator()) 212 | for _ in 1...5 { 213 | print("Random dice roll is \(d6.roll())") 214 | } 215 | // Random dice roll is 3 216 | // Random dice roll is 5 217 | // Random dice roll is 4 218 | // Random dice roll is 5 219 | // Random dice roll is 4 220 | 221 | */ 222 | 223 | //: ## 扩展里实现协议 224 | /*: 225 | 可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以实现协议的定义。 226 | 227 | 通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。 228 | 229 | protocol TextRepresentable { 230 | var textualDescription: String { get } 231 | } 232 | 233 | extension Dice: TextRepresentable { 234 | var textualDescription: String { 235 | return "A \(sides)-sided dice" 236 | } 237 | } 238 | 239 | let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator()) 240 | print(d12.textualDescription) 241 | // 打印 “A 12-sided dice” 242 | 243 | extension SnakesAndLadders: TextRepresentable { 244 | var textualDescription: String { 245 | return "A game of Snakes and Ladders with \(finalSquare) squares" 246 | } 247 | } 248 | print(game.textualDescription) 249 | // 打印 “A game of Snakes and Ladders with 25 squares” 250 | */ 251 | 252 | //: ## 条件遵循 253 | /*: 254 | 泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议。在实现纳协议的名字后面写泛型 where 分句: 255 | 256 | extension Array: TextRepresentable where Element: TextRepresentable { 257 | var textualDescription: String { 258 | let itemsAsText = self.map { $0.textualDescription } 259 | return "[" + itemsAsText.joined(separator: ", ") + "]" 260 | } 261 | } 262 | let myDice = [d6, d12] 263 | print(myDice.textualDescription) 264 | // 打印 "[A 6-sided dice, A 12-sided dice]" 265 | 266 | */ 267 | 268 | //: ## 在扩展里声明实现协议 269 | /*: 270 | 当一个类型已经符合了某个协议中的所有要求,却还没有声明实现该协议时,可以通过空扩展体的扩展遵循该协议: 271 | 272 | struct Hamster { 273 | var name: String 274 | var textualDescription: String { 275 | return "A hamster named \(name)" 276 | } 277 | } 278 | extension Hamster: TextRepresentable {} 279 | 280 | let simonTheHamster = Hamster(name: "Simon") 281 | let somethingTextRepresentable: TextRepresentable = simonTheHamster 282 | print(somethingTextRepresentable.textualDescription) 283 | // 打印 “A hamster named Simon” 284 | 285 | */ 286 | 287 | //: ## 协议的继承 288 | /*: 289 | 协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔: 290 | 291 | protocol InheritingProtocol: SomeProtocol, AnotherProtocol { 292 | // 这里是协议的定义部分 293 | } 294 | 295 | 示例: 296 | 297 | protocol PrettyTextRepresentable: TextRepresentable { 298 | var prettyTextualDescription: String { get } 299 | } 300 | 301 | extension SnakesAndLadders: PrettyTextRepresentable { 302 | var prettyTextualDescription: String { 303 | var output = textualDescription + ":\n" 304 | for index in 1...finalSquare { 305 | switch board[index] { 306 | case let ladder where ladder > 0: 307 | output += "▲ " 308 | case let snake where snake < 0: 309 | output += "▼ " 310 | default: 311 | output += "○ " 312 | } 313 | } 314 | return output 315 | } 316 | } 317 | 318 | print(game.prettyTextualDescription) 319 | // A game of Snakes and Ladders with 25 squares: 320 | // ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○ 321 | 322 | */ 323 | 324 | //: ## 类专属协议 325 | /*: 326 | 通过添加 AnyObject 或 class(貌似在4.2废弃了) 关键字到协议的继承列表,就可以限制协议只能被类类型采纳(以及非结构体或者非枚举的类型)。 327 | 328 | protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol { 329 | // 这里是类专属协议的定义部分 330 | } 331 | 332 | - Note: 333 | 声明为类专属协议的协议可以当做引用类型使用。 334 | */ 335 | 336 | //: ## 协议合成 337 | /*: 338 | 可以使用协议组合来复合多个协议为一种类型。协议组合行为就和定义的临时局部协议一样拥有构成中所有协议的定义。协议组合不定义任何新的协议类型。 339 | 340 | 协议组合使用 SomeProtocol & AnotherProtocol 的形式。可以列举任意数量的协议,用和符号(&)分开。除了协议列表,协议组合也能包含类类型。 341 | 342 | protocol Named { 343 | var name: String { get } 344 | } 345 | protocol Aged { 346 | var age: Int { get } 347 | } 348 | struct Person: Named, Aged { 349 | var name: String 350 | var age: Int 351 | } 352 | func wishHappyBirthday(to celebrator: Named & Aged) { 353 | print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!") 354 | } 355 | let birthdayPerson = Person(name: "Malcolm", age: 21) 356 | wishHappyBirthday(to: birthdayPerson) 357 | // 打印 “Happy birthday Malcolm - you're 21!” 358 | 359 | class Location { 360 | var latitude: Double 361 | var longitude: Double 362 | init(latitude: Double, longitude: Double) { 363 | self.latitude = latitude 364 | self.longitude = longitude 365 | } 366 | } 367 | class City: Location, Named { 368 | var name: String 369 | init(name: String, latitude: Double, longitude: Double) { 370 | self.name = name 371 | super.init(latitude: latitude, longitude: longitude) 372 | } 373 | } 374 | func beginConcert(in location: Location & Named) { 375 | print("Hello, \(location.name)!") 376 | } 377 | 378 | let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3) 379 | beginConcert(in: seattle) 380 | // Prints "Hello, Seattle!" 381 | 382 | */ 383 | 384 | //: ## 检查协议类型 385 | /*: 386 | 可以使用类型转换中描述的 is 和 as 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同: 387 | - is 用来检查实例是否符合某个协议,若符合则返回 true,否则返回 false 388 | - as? 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 nil 389 | - as! 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误 390 | 391 | 示例: 392 | 393 | protocol HasArea { 394 | var area: Double { get } 395 | } 396 | 397 | class Circle: HasArea { 398 | let pi = 3.1415927 399 | var radius: Double 400 | var area: Double { return pi * radius * radius } 401 | init(radius: Double) { self.radius = radius } 402 | } 403 | class Country: HasArea { 404 | var area: Double 405 | init(area: Double) { self.area = area } 406 | } 407 | 408 | class Animal { 409 | var legs: Int 410 | init(legs: Int) { self.legs = legs } 411 | } 412 | 413 | let objects: [AnyObject] = [ 414 | Circle(radius: 2.0), 415 | Country(area: 243_610), 416 | Animal(legs: 4) 417 | ] 418 | 419 | for object in objects { 420 | if let objectWithArea = object as? HasArea { 421 | print("Area is \(objectWithArea.area)") 422 | } else { 423 | print("Something that doesn't have an area") 424 | } 425 | } 426 | // Area is 12.5663708 427 | // Area is 243610.0 428 | // Something that doesn't have an area 429 | 430 | */ 431 | 432 | //: ## 可选协议 433 | /*: 434 | Swift 可以定义可选协议,遵循协议的类型可以选择是否实现这些要求。在协议中使用 optional 关键字作为前缀来定义可选协议。可选协议用在需要和 Objective-C 打交道的代码中。协议名和可选协议定义都必须带上 @objc 属性。标记 @objc 特性的协议只能被继承自 Objective-C 类的类或者 @objc 类遵循,其他类以及结构体和枚举均不能遵循这种协议。 435 | 436 | 使用可选协议时,它们的类型会自动变成可选的。比如,一个类型为 (Int) -> String 的方法会变成 ((Int) -> String)?。需要注意的是整个函数类型是可选的,而不是函数的返回值。 437 | 438 | 协议中的可选方法或属性可通过可选链调用,因为遵循协议的类型可能没有实现这些可选要求。类似 someOptionalMethod?(someArgument) 这样,可以在可选方法名称后加上 ? 来调用可选方法; 439 | 440 | @objc protocol CounterDataSource { 441 | @objc optional func incrementForCount(count: Int) -> Int 442 | @objc optional var fixedIncrement: Int { get } 443 | } 444 | 445 | class Counter { 446 | var count = 0 447 | var dataSource: CounterDataSource? 448 | func increment() { 449 | if let amount = dataSource?.incrementForCount?(count) { 450 | count += amount 451 | } else if let amount = dataSource?.fixedIncrement { 452 | count += amount 453 | } 454 | } 455 | } 456 | 457 | */ 458 | 459 | //: ## 协议扩展 460 | /*: 461 | 协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。通过这种方式,可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数。 462 | 463 | extension RandomNumberGenerator { 464 | func randomBool() -> Bool { 465 | return random() > 0.5 466 | } 467 | } 468 | 469 | let generator = LinearCongruentialGenerator() 470 | print("Here's a random number: \(generator.random())") 471 | // 打印 “Here's a random number: 0.37464991998171” 472 | print("And here's a random Boolean: \(generator.randomBool())") 473 | // 打印 “And here's a random Boolean: true” 474 | 475 | */ 476 | 477 | //: ## 提供默认实现 478 | /*: 479 | 可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。 480 | 481 | extension PrettyTextRepresentable { 482 | var prettyTextualDescription: String { 483 | return textualDescription 484 | } 485 | } 486 | */ 487 | 488 | //: ## 条件限制 489 | /*: 490 | 在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 where 子句来描述: 491 | 492 | extension Collection where Element: Equatable { 493 | func allEqual() -> Bool { 494 | for element in self { 495 | if element != self.first { 496 | return false 497 | } 498 | } 499 | return true 500 | } 501 | } 502 | 503 | let equalNumbers = [100, 100, 100, 100, 100] 504 | let differentNumbers = [100, 100, 200, 100, 200] 505 | 506 | print(equalNumbers.allEqual()) 507 | // 打印 "true" 508 | print(differentNumbers.allEqual()) 509 | // 打印 "false" 510 | 511 | */ 512 | //: [下一页](@next) 513 | -------------------------------------------------------------------------------- /SwiftWT.playground/Pages/Basics.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Basics.xcplaygroundpage 3 | // SwiftWT 4 | // 5 | // Created by devedbox. 6 | // 7 | //: [上一页](@previous) 8 | import Foundation 9 | //: # Swift基础部分 10 | /*: 11 | `Swift`和其他语言一样,也有自己的内建类型,如整型`Int`,浮点型`Float`、`Double`,布尔`Bool`,字符(串)`Charactor`、`String`以及集合类型如:`Set`、`Array`、`Dictionary`等. 12 | 13 | `Swift`和其他大部分语言一样,也是通过变量存储可变值,使用常量存储不可变值,`Swift`中的常量比`C`语言家族的要强大和简单得多,在`Swift`中,常量就是常量,常量就是不可变的值,可以是一个引用,也可以是一个结构体值类型;常量的使用贯穿了整个`Swift`语言,这有点函数式编程的味道,使用常量可以使我们的代码变得更加简单和安全. 14 | 15 | `Optional`可选类型,可以说是`Swift`的心脏,`Swift`中的很多特性甚至标准库的构建都是基于可选的,`Swift`中的可选类型类似于`OC`中的`nil`,但是远远比`OC`的`nil`要强大. 16 | */ 17 | 18 | //: ## 常量与变量 19 | /*: 20 | ### 常量与变量的声明 21 | `Swift`中常量和变量的声明很简单,`var`声明变量,`let`生命常量,而且大多数时候不需要指定类型,`Swift`强大的类型推断可以推测常量/变量的类型 22 | 23 | var variable = 1 24 | let constant = 1 25 | 26 | print(variable) // 1 27 | 28 | 同时声明多个常量/变量 29 | 30 | var a = 0.0, b = 0.0, c = 0.0 31 | let x = 0.0, y = 0.0, z = 0.0 32 | 33 | print(a) // 0.0 34 | */ 35 | var a = 0.0, b = 0.0, c = 0.0 36 | let x = 0.0, y = 0.0, z = 0.0 37 | //: ## 类型声明 38 | /*: 39 | 类型声明在`Swift`是可选的,因为`Swift`有类型推断的机制;但是在一些复杂场景下,如声明可选类型或引用复杂表达式的情况,类型声明就显得很有必要;类型声明很简单,只需要在常量/变量名后边加上冒号和类型名即可: 40 | 41 | let variable: VariableType = value 42 | var optional: OptionalType? 43 | let value: String? = regex.matches(in: contents, option: []).first?.map { $0 } 44 | 45 | 同时声明多个**相同类型**的常量/变量: 46 | 47 | var red, green, blue: Int 48 | let redc, greenc, bluec: Int 49 | 50 | red = 1 51 | redc = 1 52 | redc = 2 // Error: Change 'let' to 'var' to make it mutable. 53 | 54 | print(green) // Error: Variable 'green' used before initialized. 55 | 56 | - Note: 57 | `Swift`在安全性里做过概述,所有常量/变量必须要初始化之后才能使用. 58 | */ 59 | 60 | //: ## 变量命名 61 | /*: 62 | `Swift`变量命名可以使用几乎所有的字符,包括`Unicode`: 63 | 64 | let π = 3.14159 65 | let 你好 = "你好世界" 66 | let 🐶🐮 = "dogcow" 67 | 68 | 命名规则: 69 | - 不能包含空格、数学符号、保留的`Unicode`字符、连线符与制表符 70 | - 不能以数字开头,不能使用保留字符 71 | - 若要使用保留字符,遵循格式:\`for\` 72 | */ 73 | 74 | //: ## 打印 75 | /*: 76 | `Swift`中打印常量/变量的值使用`print`函数: 77 | 78 | let variable = 1 79 | print(variable) // 1 80 | 81 | 格式化输出:`print("%d%.2f%s", intValue, floatValue, stringValue)`: 82 | 83 | let intValue = 1 84 | let floatValue = 1.0 85 | let stringValue = "string" 86 | print(String(format: "%d-%.2f-%@", intValue, floatValue, stringValue)) // 1-1.00-string 87 | 88 | `print`函数:`func print(_ items: Any..., separator: String = default, terminator: String = default)`,可以支持自定义的打印: 89 | 90 | let intValue = 1 91 | let floatValue = 1.0 92 | let stringValue = "string" 93 | print(intValue, floatValue, stringValue) // separator默认为" " 94 | // 1 1.0 string 95 | print(a, b, c, separator: "\n") 96 | // 1 97 | // 1.0 98 | // string 99 | 100 | `Swift`还支持‘字符串插值’,当我们在字符串中插入`\(value)`时,`value`的值就会被插入字符串中: 101 | 102 | print("The current value of friendlyWelcome is \(friendlyWelcome)") 103 | // Prints "The current value of friendlyWelcome is Bonjour!" 104 | */ 105 | 106 | //: ## 注释 107 | /*: 108 | `Swift`注释跟其他语言一样,使用`//`进行注释,文档化单行注释需要使用`///`,多行注释使用`\/\*\*\/`的语法,另外,`Swift`的文档化注释支持`Markdown`的语法,对于文档注释的编写很方便也很友好: 109 | 110 | /// Another description 111 | /// 112 | /// - important: Make sure you read this 113 | /// - returns: a Llama spotter rating between 0 and 1 as a Float 114 | /// - parameter totalLlamas: The number of Llamas spotted on the trip 115 | /// 116 | /// More description 117 | 118 | `Swift`多行注释可以嵌套,使用嵌套注释可以将注释的内容模块化,方便文档化及查阅: 119 | 120 | \/\* This is the start of the first multiline comment. 121 | \/\* This is the second, nested multiline comment. \*\/ 122 | This is the end of the first multiline comment. \*\/ 123 | 124 | [Markup Formatting Reference](https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_markup_formatting_ref) 125 | */ 126 | 127 | //: ## 语句结束标识 '**`;`**' 128 | /*: 129 | 在`C`语言家族中,没一条语句结束都必须添加语句结束标识符`;`,但是在`Swift`中是可选的,`Swift`中的语句结束标识是换行符,也就是说,编写`Swift`代码的时候可以不用编写`;`;但是当一行有一个以上的语句时,就需要添加`;`用以分割: 130 | 131 | let cat = "🐱"; print(cat) 132 | // Prints "🐱" 133 | 134 | */ 135 | 136 | //: ## 整型 137 | /*: 138 | `Swift`整型类型是`Int`,代表整型数值,`Swift`提供了各种长度的整型类型: 139 | - `Int8`, `UInt8`:8位 140 | - `Int16`, `UInt16`:16位 141 | - `Int32`, `UInt32`:32位 142 | - `Int64`, `UInt64`:64位 143 | 默认`Int`是平台相关的,在32位机器上`Int`长度是32位,在64位机器上`Int`是64位. 144 | 145 | ### 获取`Int`范围 146 | `Swift`中获取`Int`范围可以通过内建属性`min`, `max`来获取: 147 | 148 | let minValue = UInt8.min // minValue is equal to 0, and is of type UInt8 149 | let maxValue = UInt8.max // maxValue is equal to 255, and is of type UInt8 150 | */ 151 | 152 | //: ## 浮点型 153 | /*: 154 | `Swift`中浮点型是`Float`和`Double`,`Float`精度是6,`Double`的精度是15. `Float`的长度是32位,`Double`的长度64位. 155 | */ 156 | 157 | //: ## 类型安全与类型推断 158 | /*: 159 | `Swift`是类型安全的语言,对类型特别敏感,一个`String`类型的变量不会接受非`String`类型的值,`Swift`编译器会在编译期间进行类型检查,并将不匹配的类型标记为错误;`Swift`的类型推断特性可以在不声明变量类型的时候通过变量值推断出变量的类型,这在简单的场景下非常实用,减少了一定的代码量,但是在复杂情况下还是最好声明变量的类型,这样有利于代码的可读性: 160 | 161 | let meaningOfLife = 42 162 | // meaningOfLife is inferred to be of type Int 163 | let pi = 3.14159 164 | // pi is inferred to be of type Double 165 | let anotherPi = 3 + 0.14159 166 | // anotherPi is also inferred to be of type Double 167 | 168 | 需要注意的是,`Swift`在进行类型推断的时候为了做兼容性处理,永远会把浮点型字面量推断为`Double`类型. 169 | */ 170 | 171 | //: ## 数值字面量 172 | /*: 173 | 数值字面量可以使用10进制、2进制、8进制、16进制进行编写: 174 | - 10进制:数值无前缀 175 | - 2进制:`0b`+数值 176 | - 8进制:`0o`+数值 177 | - 16进制:`0x`+数值 178 | 179 | 浮点型数值字面量也可以使用10进制和16进制,在小数点两边都必须要有至少一个数值(不像OC可以写作.3f);10进制的浮点数可以有一个可选的指数`e`来标记分位,16进制则必须有一个指数来标记分位,使用`P`或`p`来标记: 180 | 10进制标记指数: 181 | 182 | 1.25e2 means 1.25 x 102, or 125.0. 183 | 1.25e-2 means 1.25 x 10-2, or 0.0125. 184 | 185 | 16进制标记指数: 186 | 187 | 0xFp2 means 15 x 22, or 60.0. 188 | 0xFp-2 means 15 x 2-2, or 3.75. 189 | 190 | 以下都表示浮点数`12.1875`: 191 | 192 | let decimalDouble = 12.1875 193 | let exponentDouble = 1.21875e1 194 | let hexadecimalDouble = 0xC.3p0 195 | 196 | 数值字面量可以通过额外的格式化增加可读性,可以通过额外的`0`或下划线`_`来格式化字面量: 197 | 198 | let paddedDouble = 000123.456 // 在前面添加`0` 199 | let oneMillion = 1_000_000 // 下划线分隔 200 | let justOverOneMillion = 1_000_000.000_000_1 201 | 202 | */ 203 | 204 | //: ## 数值类型转换 205 | /*: 206 | 一般情况下,在`Swift`使用整型的时候尽量使用`Int`,默认的`Int`编译器做了优化,可以直接被复用,而且更加利于类型推断系统的运作;只有在明确需要用到其他整型类型的时候,才需要制定其他类型,如明确数值长度、性能优化等. `Swift`中默认的不同的数值类型不能直接做加减乘除运算,除非重载运算符以实现. 数值字面量的类型是不定的,只有在编译器做类型推导的时机才会确定数值字面量的类型: 207 | 208 | let pi = 3 + 0.141592654 // 因为0.141592654是Double类型,因此3会被推断为Double而不是Int 209 | 210 | let three = 3 211 | let pointOneFourOneFiveNine = 0.14159 212 | let pi = three + pointOneFourOneFiveNine // Error: note: overloads for '+' exist with these partially matching parameter lists: (Double, Double), (Int, Int), (Date, TimeInterval), (DispatchTime, Double), (DispatchWallTime, Double), (Int, UnsafeMutablePointer), (Int, UnsafePointer) 213 | 214 | ### 整型转换 215 | 在`Swift`中类型是敏感的,`Int8`类型的值是不能直接赋值给`Int32`的,因为这在`Swift`中是两个完全不同的类型,如果需要将`Int8`的值赋值给`Int32`的变量,则需要显式的进行类型转换: 216 | 217 | let int8: Int8 = 127 218 | let int32: Int32 = int8 // Error: cannot convert value of type 'Int8' to specified type 'Int32' 219 | let int32: Int32 = Int32(int8) 220 | 221 | 数值类型的转换就是通过调用对应数值的初始化方法,将需要转换的数值作为参数传递即可. 222 | - Note: 223 | 将长度高的数据转换为长度第的数值可能会失败,造成运行时错误: 224 | 225 | let int32: Int32 = 1000000 226 | let int8: Int8 = Int8(int32) // Fatal error: Not enough bits to represent a signed value 227 | 228 | ### 整型与浮点型转换 229 | 整型和浮点数进行转换也是同样的方式,调用需要转换类型的初始化方法,将需要转换的变量作为参数传入即可: 230 | 231 | let three = 3 232 | let pointOneFourOneFiveNine = 0.14159 233 | let pi = Double(three) + pointOneFourOneFiveNine 234 | // pi equals 3.14159, and is inferred to be of type Double 235 | */ 236 | let aa = 2 237 | let bb = 2.0 238 | let ddd = Double(aa) + bb 239 | //: ## 类型别名 240 | /*: 241 | 类似`C`语言家族,`Swift`也有类型别名的语法,通过指定别名可以使别名拥有和被指定别名的类型一样的功效: 242 | 243 | typealias AudioSample = UInt16 244 | 245 | var maxAmplitudeFound = AudioSample.min 246 | // maxAmplitudeFound is now 0 247 | 248 | 别名声明之后,可以在别名作用域内的任何地方使用. 249 | */ 250 | 251 | //: ## 布尔类型 252 | /*: 253 | `Swift`中的布尔值不同于其他语言的布尔值,大部分其他语言中的布尔其实也是一个整型值,通过`0`与`!0`来表示真与假,`Swift`中的布尔值只有两个值:`true`、`false`,而且不能与其他类型转换. 需要注意的是,在`Swift`中使用流程控制语法,条件体中只能是布尔类型的值,不接受其他类型的值: 254 | 255 | let i = 1 256 | if i { 257 | // this example will not compile, and will report an error 258 | } 259 | 260 | let i = 1 261 | if i == 1 { 262 | // this example will compile successfully 263 | } 264 | 265 | */ 266 | 267 | //: ## 元组类型 268 | /*: 269 | 元组可以将多个其他类型的值联合在一起作为一个新的类型的值,这个新的类型就是元组,因此元组是复合类型,元组的类型是由组成元组的类型决定的,组成元组类型的类型可以是任何类型和任意多的类型: 270 | 271 | let http404Error = (404, "Not Found") 272 | // http404Error is of type (Int, String), and equals (404, "Not Found") 273 | 274 | 元组可以作为`Swift`中的类型来使用,和使用其他类型一样,但是元组只适合数据较少的场景,对于数据较大的场景,不推荐使用. 275 | */ 276 | 277 | /*: 278 | ### 元组类型的定义 279 | 定义一个元组类型很简单,使用括号将联合的类型声明在一起即可: 280 | 281 | typealias Tuple = (Int, Float, Double, String) // 无标签 282 | typealias Tuple = (int: Int, float: Float, double: Double, string: String) // 标签 283 | */ 284 | 285 | /*: 286 | ### 元组的分解 287 | - 元组是由若干个其他类型的值联合而成的值,因此元组的分解也很简单,使用变量或常量声明的语法将值**一一对应**起来即可: 288 | 289 | let (statusCode, statusMessage) = http404Error 290 | print("The status code is \(statusCode)") 291 | // Prints "The status code is 404" 292 | print("The status message is \(statusMessage)") 293 | // Prints "The status message is Not Found" 294 | 295 | - 可以使用下划线 `_` **忽略匹配**: 296 | 297 | let (justTheStatusCode, _) = http404Error 298 | print("The status code is \(justTheStatusCode)") 299 | // Prints "The status code is 404" 300 | 301 | - 可以使用**下标位置**取值: 302 | 303 | print("The status code is \(http404Error.0)") 304 | // Prints "The status code is 404" 305 | print("The status message is \(http404Error.1)") 306 | // Prints "The status message is Not Found" 307 | 308 | - 可以使用**标签**: 309 | 310 | let http200Status = (statusCode: 200, description: "OK") 311 | print("The status code is \(http200Status.statusCode)") 312 | // Prints "The status code is 200" 313 | print("The status message is \(http200Status.description)") 314 | // Prints "The status message is OK" 315 | */ 316 | 317 | //: ## 可选类型 318 | /*: 319 | 在`Swift`中,所有类型的变量或实例都只接受对应类型的值,还有一种情况就是值为**空**`(nil)`的情况,这个时候,就需要将类型声明为`Optional`的类型. `Optional`类型的变量/常量可以接受对应Wrapped的类型的值和`nil`,`nil`表示无值、空缺,`Optional`类型可以表示任何类型;在`C`和`OC`中也有`nil`和`NULL`的概念,表示空指针,这个和`Swift`中的`Optional`是不同的概念;`Optional`类型是`Swift`语言的心脏,`Swift`的很多特性和标准库都是基于`Optional`类型构建起来的. 320 | 321 | `Swift`可选类型的实现很简单,`Optional`其实就是一个泛型枚举,枚举的定义如下: 322 | 323 | public enum Optional: ExpressibleByNilLiteral { 324 | case nil 325 | case some(Wrapped) 326 | } 327 | 328 | 可选类型的声明两种方式: 329 | - 使用标准类型声明: 330 | 331 | var variable: Optional 332 | 333 | - 使用语法糖进行声明: 334 | 335 | var variable: Int? 336 | 337 | */ 338 | 339 | /*: 340 | ### `nil` 341 | 通过给可选类型设值`nil`以表示值缺失: 342 | 343 | var serverResponseCode: Int? = 404 344 | // serverResponseCode contains an actual Int value of 404 345 | serverResponseCode = nil 346 | // serverResponseCode now contains no value 347 | 348 | 当声明一个可选类型的变量时,这个变量的默认值为`nil`: 349 | 350 | var surveyAnswer: String? 351 | // surveyAnswer is automatically set to nil 352 | 353 | */ 354 | 355 | /*: 356 | ## 条件判断与强制解包 357 | 声明为可选类型的变量不能直接使用,需要解包之后才能使用;可选类型变量可以通过判断是否为`nil`来进行强制解包,不过请注意,如果可选类型变量为`nil`,强制解包会触发运行时错误,因此在使用可选类型的时候,尤其是在强制解包的时候,一定要确保可选类型值不为`nil`;强制解包的语法很简单,只需要在变量名后边加上`!`即可: 358 | 359 | if convertedNumber != nil { 360 | print("convertedNumber has an integer value of \(convertedNumber!).") 361 | } 362 | // Prints "convertedNumber has an integer value of 123." 363 | */ 364 | 365 | /*: 366 | ## 可选绑定 367 | 我们在处理可选类型的时候,需要明确确定有值才能解包,除了强制解包之外,可选类型还可以使用**可选绑定**的语法进行解包,这是一种安全的做法,可选绑定通过自动匹配可选类型变量的值,当值不为空是才解包;可选绑定的模式在`Swift`中很常见,语法: 368 | 369 | if let constantName = someOptional { 370 | statements 371 | } 372 | 373 | 除了在`if`条件判断中使用,还可以在`while`、`guard`等流程控制中使用;一个流程控制域中可以同时进行多个可选绑定,只需要使用逗号 `,` 分隔开即可: 374 | 375 | if let actualNumber = Int(possibleNumber) { 376 | print("\"\(possibleNumber)\" has an integer value of \(actualNumber)") 377 | } else { 378 | print("\"\(possibleNumber)\" could not be converted to an integer") 379 | } 380 | // Prints ""123" has an integer value of 123" 381 | 382 | if let actualNumber = Int(possibleNumber), let secondActualNumber = actualNumber.someOptionalNumber { 383 | // statements 384 | } 385 | 386 | while let next = iterator.next() { 387 | // statements 388 | } 389 | 390 | 可选绑定除了可以使用常量绑定之外,还可以使用变量绑定: 391 | 392 | if var actualNumber = somePossibleNumner { 393 | actualNumber = anotherPossibleNumber 394 | } 395 | 396 | */ 397 | let op: Int? = 1 398 | let val = 1 399 | 400 | if let _ = op { 401 | 402 | } 403 | 404 | if op != nil { 405 | 406 | } 407 | 408 | if let cval = val { 409 | 410 | } 411 | //: ## 隐式解析的可选类型 412 | /*: 413 | 隐式解析的可选类型是一种特殊的可选类型,隐式解析可选的类型的变量在使用的时候不用显式的解包,当在使用隐式解析可选类型的变量的时候,隐式解析可选类型会自动帮我们解包: 414 | 415 | let possibleString: String? = "An optional string." 416 | let forcedString: String = possibleString! // requires an exclamation mark 417 | 418 | let assumedString: String! = "An implicitly unwrapped optional string." 419 | let implicitString: String = assumedString // no need for an exclamation mark 420 | 421 | 隐式解析可选可以用在当我们确定变量在初始化之后或者经过一系列处理之后不可能为空的情况下使用,这种时候隐式解析可选就完全可以当做普通类型来使用;相反,如果隐式解析可选变量是`nil`,我们在使用的时候就会触发运行时错误,这是我们需要注意的地方. 422 | 423 | 隐式解析可选类型和可选类型类似,也是一个泛型枚举类型,叫做`ImplicitlyUnwrappedOptional`,定义和`Optional`一样,所以在声明的时候就可以使用`var assumedString: ImplicitlyUnwrappedOptional`的语法进行声明,同时,也可以使用语法糖简写:`var assumedString: String!` 424 | 425 | 另外,隐式解析可选类型兼容可选类型,可以当做可选类型使用. 426 | */ 427 | 428 | //: ## 错误处理 429 | /*: 430 | `Swift`中的错误处理简单而强大,通过协议`Error`定义错误类型,自定义错误类型只需要实现`Error`即可;`Swift`通过一系列的语法特性,使构建错误处理流程边的异常简单,在`Swift`中,如果一个函数在执行期间会产生错误信息,那么这个函数必须要声明成为"可错误"函数,通过管检测`throws`声明:`func functionCanThrow() throws -> Void`,通过这个标记,使函数在调用方看起来简明易懂,在实现函数的时候,只需要在必要的地方使用语法`throw Error`抛出错误信息即可. 431 | 432 | 被标记为`throws`的函数在调用时,必须处理错误信息,`Swift`中的错误处理语法: 433 | 434 | do { 435 | try ... 436 | } catch { 437 | ... 438 | } 439 | 440 | 错误信息会被抛出到`catch`块进行处理. 441 | 442 | 在进行错误处理的时候,可以使用模式匹配的语法,如: 443 | 444 | func makeASandwich() throws { 445 | // ... 446 | } 447 | 448 | do { 449 | try makeASandwich() 450 | eatASandwich() 451 | } catch SandwichError.outOfCleanDishes { 452 | washDishes() 453 | } catch SandwichError.missingIngredients(let ingredients) { 454 | buyGroceries(ingredients) 455 | } 456 | 457 | 将需要匹配的错误类型以及额外信息放置在`catch`语句后即可. 458 | */ 459 | let array = [1, 3, 4] 460 | array[3] 461 | //: ## 断言和前置条件 462 | /*: 463 | 断言和前置条件可以帮助我们调试程序,不满足断言或者前置条件的代码将会触发运行时错误,和错误处理不一样,断言和前置条件不能被捕获;断言只在`Debug`环境下生效,前置条件在`Debug`和`Release`都能生效,因此,在开发过程中可以使用任意数量的断言,但是前置条件就需要慎重使用. 464 | 465 | ### 断言使用 466 | 断言函数是`Swift`标准库提供的全局函数:`assert(_:_:file:line:)`,第一个参数和第二个参数分别是断言条件和调试信息(可选),当条件为真时程序才继续往下执行: 467 | 468 | let age = -3 469 | assert(age >= 0, "A person's age can't be less than zero.") 470 | // This assertion fails because -3 is not >= 0. 471 | 472 | ### 先决条件 473 | 先决条件和断言一样,也是`Swift`标准库提供的全局函数:`precondition(_:_:file:line:)`,使用上没什么区别. 474 | 475 | // In the implementation of a subscript... 476 | precondition(index > 0, "Index must be greater than zero.") 477 | 478 | 需要注意的地方: 479 | - `precondition`将会被`-Ounchecked`模式禁用,`-Ounchecked`模式下所有的`precondition`都将判真. 480 | - 需要忽略`-Ounchecked`的影响,可以使用`fatalError(_:file:line:)`函数. 481 | */ 482 | //: [下一页](@next) 483 | --------------------------------------------------------------------------------