Swift 5.9 知识整理 Done
2024-04-27 23:07:02 0 举报
AI智能生成
Swift 基础 语法 面试 知识 整理 汇总
作者其他创作
大纲/内容
大纲
感悟
编程语言为什么这样命名,本质它是一门语。那么我们写代码是什么,和一部书一样。
语法:
词法:
值:字面量
量:变量var、常量let,区别?
型:数据类型:
分为:
值类型:基本数据类型、集合结构体、元组、枚举、区间?;
引用类型:类、和闭包?;
区别:值类型和引用类型有以下几个方面的区别:
数据存储:独立数据副本 共享数据副本
赋值方式:深拷贝 浅拷贝
线程安全性:安全 不安全
内存占用:较高 较低
性能:较高 较低
易用性:易用 复杂
内存泄漏风险:低 高
符:标识符
标点:标点符号
句法:普通句子、控制流程语句
段法:方法
小节:扩展(OC中的分类、扩展;Dart中的extension、minix;)
节法:类
章法:包
书法:项目
语法:
词法:
值:字面量
量:变量var、常量let,区别?
型:数据类型:
分为:
值类型:基本数据类型、集合结构体、元组、枚举、区间?;
引用类型:类、和闭包?;
区别:值类型和引用类型有以下几个方面的区别:
数据存储:独立数据副本 共享数据副本
赋值方式:深拷贝 浅拷贝
线程安全性:安全 不安全
内存占用:较高 较低
性能:较高 较低
易用性:易用 复杂
内存泄漏风险:低 高
符:标识符
标点:标点符号
句法:普通句子、控制流程语句
段法:方法
小节:扩展(OC中的分类、扩展;Dart中的extension、minix;)
节法:类
章法:包
书法:项目
playground的使用
子主题
pdf阅读
01、基础语法:语句构成五要素
关
关键字
值
字面量
字面量是值
let a = true true就是一个字面量,例如7、8、9这样的数字也是字面量
量
是什么
量是指变量和常量,它们用于存储和处理数据
分类
常量(Constants)
使用 let 关键字声明,其值一旦被赋值就不能再改变。适合用于存储不需要改变的数据
变量(Variables)
使用 var 关键字声明,其值可以随时更改。适合用于存储可能会变化的数据。
型
数据类型
分类
根据赋值方式分为值类型和引用类型
值类型
基本数据类型
本质上都是结构体
布尔
bool
整型
Int
浮点
Float
Double
字符
Character
字符串
String
枚举
单独的类型,不是结构体
在 Swift 中,枚举(Enum)不是结构体,而是一种单独的数据类型。尽管枚举和结构体都属于值类型,但它们之间有着明显的区别。
枚举是一种用于定义一组相关值的类型。它可以包含零个或多个成员,并且每个成员都可以关联一个或多个值。与结构体和类不同,枚举成员之间没有直接的关系,而是在逻辑上相关。枚举通常用于定义一组有限的可能性,例如表示状态、选项、错误等。
下面是一个简单的枚举示例:
```swift
enum CompassPoint {
case north
case south
case east
case west
}
var direction = CompassPoint.north
direction = .east
```
在这个例子中,`CompassPoint` 是一个枚举,它包含了四个成员:`north`、`south`、`east` 和 `west`。变量 `direction` 的类型是 `CompassPoint`,可以赋值为枚举中的任何一个成员。
虽然枚举和结构体都是值类型,但它们的用途和设计目的有所不同。枚举更适合于表示一组相关的值,而结构体则更适合于表示一个具有多个属性的实体。
枚举是一种用于定义一组相关值的类型。它可以包含零个或多个成员,并且每个成员都可以关联一个或多个值。与结构体和类不同,枚举成员之间没有直接的关系,而是在逻辑上相关。枚举通常用于定义一组有限的可能性,例如表示状态、选项、错误等。
下面是一个简单的枚举示例:
```swift
enum CompassPoint {
case north
case south
case east
case west
}
var direction = CompassPoint.north
direction = .east
```
在这个例子中,`CompassPoint` 是一个枚举,它包含了四个成员:`north`、`south`、`east` 和 `west`。变量 `direction` 的类型是 `CompassPoint`,可以赋值为枚举中的任何一个成员。
虽然枚举和结构体都是值类型,但它们的用途和设计目的有所不同。枚举更适合于表示一组相关的值,而结构体则更适合于表示一个具有多个属性的实体。
元组
单独的类型,不是结构体
在 Swift 中,元组(Tuple)不是结构体。它们是一种轻量级的数据结构,用于在单个值中存储多个相关的值。元组在 Swift 中被广泛用于返回函数多个值、临时组织相关数据等场景。
与结构体不同,元组在编译时被视为一个单独的数据类型。你可以使用元组来临时组织数据,而无需为其定义新的自定义类型。
元组的类型是根据其包含的元素的数量和类型来确定的,因此元组的类型可以是异构的,即它可以包含不同类型的值。
下面是一个元组的例子:
```swift
let person = (name: "John", age: 30)
print(person.name) // 输出: John
print(person.age) // 输出: 30
```
在这个例子中,`person` 是一个包含两个元素的元组,第一个元素的标签是 `name`,第二个元素的标签是 `age`。元组的类型是由编译器自动推断的,因为它包含一个 `String` 类型的元素和一个 `Int` 类型的元素。
总之,元组在 Swift 中是一种独特的数据类型,与结构体不同,它们不需要显式的定义和命名,且元组的类型是由其元素的数量和类型决定的。
与结构体不同,元组在编译时被视为一个单独的数据类型。你可以使用元组来临时组织数据,而无需为其定义新的自定义类型。
元组的类型是根据其包含的元素的数量和类型来确定的,因此元组的类型可以是异构的,即它可以包含不同类型的值。
下面是一个元组的例子:
```swift
let person = (name: "John", age: 30)
print(person.name) // 输出: John
print(person.age) // 输出: 30
```
在这个例子中,`person` 是一个包含两个元素的元组,第一个元素的标签是 `name`,第二个元素的标签是 `age`。元组的类型是由编译器自动推断的,因为它包含一个 `String` 类型的元素和一个 `Int` 类型的元素。
总之,元组在 Swift 中是一种独特的数据类型,与结构体不同,它们不需要显式的定义和命名,且元组的类型是由其元素的数量和类型决定的。
结构体
集合:本质是结构体
Array
Dictionary
Set
区间类型
单独类型,非结构体
分类
按照类型分
整形区间
字符、字符串区间
不可以用在for-in
按照区间范围
闭区间
let closedRange = 1...5 // 包含 1, 2, 3, 4, 5
半开区间
let halfOpenRange = 1..<5 // 包含 1, 2, 3, 4
单侧区间
let range3 : PartialRangeThrough<Int> = ...5 // 无限根据数组的边界确定
间隔区间
for tickMark in stride(from:0, through: 10, by:2) {} //从4开始,累加2,不超过10。2、4、6、8、10
总结
...闭区间,包含上下限;..<:半开区间,不包含上限;
讲解
在 Swift 中,区间类型主要分为闭区间、半开区间和单侧区间。下面将分别介绍它们的分类以及使用方法:
### 1. 闭区间(Closed Range)
闭区间包含起始值和结束值,表示范围从起始值到结束值的所有值。闭区间使用 `...` 符号表示。
#### 使用方法:
```swift
let closedRange = 1...5 // 包含 1, 2, 3, 4, 5
// 遍历闭区间内的值
for i in closedRange {
print(i) // 打印 1, 2, 3, 4, 5
}
// 判断值是否在闭区间内
let value = 3
if closedRange.contains(value) {
print("\(value) 在闭区间内")
}
```
### 2. 半开区间(Half-Open Range)
半开区间包含起始值但不包含结束值,表示范围从起始值到结束值之前的所有值。半开区间使用 `..<` 符号表示。
#### 使用方法:
```swift
let halfOpenRange = 1..<5 // 包含 1, 2, 3, 4
// 遍历半开区间内的值
for i in halfOpenRange {
print(i) // 打印 1, 2, 3, 4
}
// 判断值是否在半开区间内
let value = 3
if halfOpenRange.contains(value) {
print("\(value) 在半开区间内")
}
```
### 3. 单侧区间(One-Sided Range)
单侧区间表示从某个方向上的所有值,可以是从起始值到序列的末尾或从序列的开头到结束值。单侧区间使用 `...` 或 `..<` 符号表示。
#### 使用方法:
```swift
let numbers = [1, 2, 3, 4, 5]
// 从索引为 2 的元素开始遍历到数组末尾
for number in numbers[2...] {
print(number) // 输出:3, 4, 5
}
// 从数组开头遍历到索引为 2 的元素(不包括索引为 2 的元素)
for number in numbers[..<2] {
print(number) // 输出:1, 2
}
```
### 4. 间隔区间(Stride Range)
间隔区间允许您指定一个范围以及一个步长(间隔),以便在范围内生成一系列值。间隔区间可以是从较小的值递增到较大的值,也可以是从较大的值递减到较小的值。您可以使用 `stride(from:to:by:)` 或 `stride(from:through:by:)` 函数来创建间隔区间。
#### 使用方法:
```swift
// 从 0 到 10(不包括 10),间隔为 2
let strideRange1 = stride(from: 0, to: 10, by: 2) // 包含 0, 2, 4, 6, 8
// 遍历间隔区间
for i in strideRange1 {
print(i) // 输出:0, 2, 4, 6, 8
}
// 从 0 到 10(包括 10),间隔为 3
let strideRange2 = stride(from: 0, through: 10, by: 3) // 包含 0, 3, 6, 9
// 遍历间隔区间
for i in strideRange2 {
print(i) // 输出:0, 3, 6, 9
}
```
以上示例中,`stride(from:to:by:)` 函数用于创建一个从起始值到结束值的间隔区间,但不包括结束值;而 `stride(from:through:by:)` 函数用于创建一个从起始值到结束值的间隔区间,包括结束值。在创建间隔区间时,您可以指定步长(间隔)以确定生成序列中的下一个值。
通过这种方式,您可以灵活地指定一系列连续的值范围,并根据需要以指定的间隔遍历这些值。
### 1. 闭区间(Closed Range)
闭区间包含起始值和结束值,表示范围从起始值到结束值的所有值。闭区间使用 `...` 符号表示。
#### 使用方法:
```swift
let closedRange = 1...5 // 包含 1, 2, 3, 4, 5
// 遍历闭区间内的值
for i in closedRange {
print(i) // 打印 1, 2, 3, 4, 5
}
// 判断值是否在闭区间内
let value = 3
if closedRange.contains(value) {
print("\(value) 在闭区间内")
}
```
### 2. 半开区间(Half-Open Range)
半开区间包含起始值但不包含结束值,表示范围从起始值到结束值之前的所有值。半开区间使用 `..<` 符号表示。
#### 使用方法:
```swift
let halfOpenRange = 1..<5 // 包含 1, 2, 3, 4
// 遍历半开区间内的值
for i in halfOpenRange {
print(i) // 打印 1, 2, 3, 4
}
// 判断值是否在半开区间内
let value = 3
if halfOpenRange.contains(value) {
print("\(value) 在半开区间内")
}
```
### 3. 单侧区间(One-Sided Range)
单侧区间表示从某个方向上的所有值,可以是从起始值到序列的末尾或从序列的开头到结束值。单侧区间使用 `...` 或 `..<` 符号表示。
#### 使用方法:
```swift
let numbers = [1, 2, 3, 4, 5]
// 从索引为 2 的元素开始遍历到数组末尾
for number in numbers[2...] {
print(number) // 输出:3, 4, 5
}
// 从数组开头遍历到索引为 2 的元素(不包括索引为 2 的元素)
for number in numbers[..<2] {
print(number) // 输出:1, 2
}
```
### 4. 间隔区间(Stride Range)
间隔区间允许您指定一个范围以及一个步长(间隔),以便在范围内生成一系列值。间隔区间可以是从较小的值递增到较大的值,也可以是从较大的值递减到较小的值。您可以使用 `stride(from:to:by:)` 或 `stride(from:through:by:)` 函数来创建间隔区间。
#### 使用方法:
```swift
// 从 0 到 10(不包括 10),间隔为 2
let strideRange1 = stride(from: 0, to: 10, by: 2) // 包含 0, 2, 4, 6, 8
// 遍历间隔区间
for i in strideRange1 {
print(i) // 输出:0, 2, 4, 6, 8
}
// 从 0 到 10(包括 10),间隔为 3
let strideRange2 = stride(from: 0, through: 10, by: 3) // 包含 0, 3, 6, 9
// 遍历间隔区间
for i in strideRange2 {
print(i) // 输出:0, 3, 6, 9
}
```
以上示例中,`stride(from:to:by:)` 函数用于创建一个从起始值到结束值的间隔区间,但不包括结束值;而 `stride(from:through:by:)` 函数用于创建一个从起始值到结束值的间隔区间,包括结束值。在创建间隔区间时,您可以指定步长(间隔)以确定生成序列中的下一个值。
通过这种方式,您可以灵活地指定一系列连续的值范围,并根据需要以指定的间隔遍历这些值。
是结构体吗?不是
在 Swift 中,区间类型(Range Type)不是结构体,而是一种范围的表示形式。Swift 中有两种主要类型的区间:闭区间和半开区间。
闭区间使用 `...` 操作符表示,包括了其开始值和结束值。例如:`1...5` 包含了 1、2、3、4 和 5 这五个值。
半开区间使用 `..<` 操作符表示,包含了其开始值但不包含结束值。例如:`1..<5` 包含了 1、2、3 和 4 这四个值。
虽然区间在 Swift 中被视为一种类型,但它们并不是结构体或类。它们只是一种用来表示范围的语法结构。当你使用区间来迭代数组、集合或者执行其他类似的操作时,它们会被转换为适当的遍历序列,但并不是真正的集合类型。
例如,你可以使用区间来遍历一个范围内的数字:
```swift
for i in 1...5 {
print(i) // 输出: 1 2 3 4 5
}
for j in 1..<5 {
print(j) // 输出: 1 2 3 4
}
```
在这两个例子中,`1...5` 和 `1..<5` 都是区间类型,它们并不是结构体或其他值类型。它们只是用来表示范围的语法结构。
闭区间使用 `...` 操作符表示,包括了其开始值和结束值。例如:`1...5` 包含了 1、2、3、4 和 5 这五个值。
半开区间使用 `..<` 操作符表示,包含了其开始值但不包含结束值。例如:`1..<5` 包含了 1、2、3 和 4 这四个值。
虽然区间在 Swift 中被视为一种类型,但它们并不是结构体或类。它们只是一种用来表示范围的语法结构。当你使用区间来迭代数组、集合或者执行其他类似的操作时,它们会被转换为适当的遍历序列,但并不是真正的集合类型。
例如,你可以使用区间来遍历一个范围内的数字:
```swift
for i in 1...5 {
print(i) // 输出: 1 2 3 4 5
}
for j in 1..<5 {
print(j) // 输出: 1 2 3 4
}
```
在这两个例子中,`1...5` 和 `1..<5` 都是区间类型,它们并不是结构体或其他值类型。它们只是用来表示范围的语法结构。
引用类型
类
函数类型
是什么
在 Swift 中,函数类型是一种表示函数的类型。函数类型由其参数类型和返回类型组成。例如,`(Int, Int) -> Int` 表示接受两个整数参数并返回一个整数结果的函数类型。
在 Swift 中,你可以将函数类型声明为变量、常量或者作为函数的参数和返回值。这使得函数在 Swift 中成为一等公民,具有与其他类型相同的地位和灵活性。你可以像操作其他类型一样对函数类型进行操作,例如将函数类型作为参数传递给函数、从函数中返回函数类型等。
函数类型的语法形式为:
```
(parameterTypes) -> returnType
```
其中 `parameterTypes` 是参数类型列表,用逗号分隔;`returnType` 是返回类型。例如,`(Int, Int) -> Int` 表示接受两个整数参数并返回一个整数结果的函数类型。
除了普通的函数类型,Swift 还支持嵌套函数、泛型函数以及带有捕获上下文的闭包等。这些特性使得函数在 Swift 中具有非常丰富的表现力和灵活性。
在 Swift 中,你可以将函数类型声明为变量、常量或者作为函数的参数和返回值。这使得函数在 Swift 中成为一等公民,具有与其他类型相同的地位和灵活性。你可以像操作其他类型一样对函数类型进行操作,例如将函数类型作为参数传递给函数、从函数中返回函数类型等。
函数类型的语法形式为:
```
(parameterTypes) -> returnType
```
其中 `parameterTypes` 是参数类型列表,用逗号分隔;`returnType` 是返回类型。例如,`(Int, Int) -> Int` 表示接受两个整数参数并返回一个整数结果的函数类型。
除了普通的函数类型,Swift 还支持嵌套函数、泛型函数以及带有捕获上下文的闭包等。这些特性使得函数在 Swift 中具有非常丰富的表现力和灵活性。
闭包
问题
闭包是类吗
在 Swift 中,闭包类型不是类,它们是一种特殊的函数类型。虽然闭包在某种程度上类似于匿名函数,但它们不同于类或结构体。
闭包在 Swift 中具有自己的类型,由于 Swift 是一种强类型语言,因此闭包类型是编译时确定的。你可以像声明其他变量和常量类型一样声明闭包类型,并将闭包类型作为函数的参数、返回值或者赋值给变量或常量。
在 Swift 中,闭包类型的声明通常是这样的:
```swift
(parameters) -> ReturnType
```
其中 `parameters` 是闭包的参数列表,`ReturnType` 是闭包的返回类型。例如:
```swift
() -> Void
```
表示没有参数且没有返回值的闭包类型。
虽然闭包类型与类或结构体类型不同,但你可以像操作其他类型一样对闭包类型进行操作,例如传递给函数、从函数返回、赋值给变量等。这使得闭包在 Swift 中成为一等公民,具有与其他类型相同的地位和灵活性。
闭包在 Swift 中具有自己的类型,由于 Swift 是一种强类型语言,因此闭包类型是编译时确定的。你可以像声明其他变量和常量类型一样声明闭包类型,并将闭包类型作为函数的参数、返回值或者赋值给变量或常量。
在 Swift 中,闭包类型的声明通常是这样的:
```swift
(parameters) -> ReturnType
```
其中 `parameters` 是闭包的参数列表,`ReturnType` 是闭包的返回类型。例如:
```swift
() -> Void
```
表示没有参数且没有返回值的闭包类型。
虽然闭包类型与类或结构体类型不同,但你可以像操作其他类型一样对闭包类型进行操作,例如传递给函数、从函数返回、赋值给变量等。这使得闭包在 Swift 中成为一等公民,具有与其他类型相同的地位和灵活性。
详见03、中的函数类型子分支
详介
在 Swift 中,引用类型包括:
* 类
* 闭包
* 泛型类型
* 可变类型,例如 `var` 和 `let`
## 类
类是引用类型,因为它们是通过引用传递的。这意味着当您将类实例赋值给另一个变量或将其作为参数传递给函数时,将创建对该实例的引用。
以下是一些示例:
```swift
// 创建一个类实例
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
var person1 = Person(name: "John Doe", age: 30)
// 将类实例赋值给另一个变量
var person2 = person1
// 将类实例作为参数传递给函数
func printPerson(person: Person) {
print(person.name)
print(person.age)
}
printPerson(person: person1)
```
在此示例中,`person2` 是 `person1` 的引用。对 `person2` 所做的任何更改都会影响 `person1`。
## 闭包
闭包是引用类型,因为它们是通过引用传递的。这意味着当您将闭包赋值给另一个变量或将其作为参数传递给函数时,将创建对该闭包的引用。
以下是一些示例:
```swift
// 创建一个闭包
let closure = { (name: String) -> String in
return "Hello, \(name)!"
}
// 将闭包赋值给另一个变量
let anotherClosure = closure
// 将闭包作为参数传递给函数
func printGreeting(closure: (String) -> String) {
let greeting = closure("John Doe")
print(greeting)
}
printGreeting(closure: closure)
```
在此示例中,`anotherClosure` 是 `closure` 的引用。对 `anotherClosure` 所做的任何更改都会影响 `closure`。
## 泛型类型
泛型类型是引用类型,因为它们是通过引用传递的。这意味着当您将泛型类型实例赋值给另一个变量或将其作为参数传递给函数时,将创建对该实例的引用。
以下是一些示例:
```swift
// 创建一个泛型类型实例
struct Array<T> {
var elements: [T]
init(elements: [T]) {
self.elements = elements
}
}
var array1 = Array(elements: [1, 2, 3])
// 将泛型类型实例赋值给另一个变量
var array2 = array1
// 将泛型类型实例作为参数传递给函数
func printArray(array: Array<Int>) {
for element in array.elements {
print(element)
}
}
printArray(array: array1)
```
在此示例中,`array2` 是 `array1` 的引用。对 `array2` 所做的任何更改都会影响 `array1`。
## 可变类型
可变类型是引用类型,因为它们是通过引用传递的。这意味着当您将可变类型赋值给另一个变量或将其作为参数传递给函数时,将创建对该变量的引用。
以下是一些示例:
```swift
// 创建一个可变类型
var number = 1
// 将可变类型赋值给另一个变量
var anotherNumber = number
// 将可变类型作为参数传递给函数
func printNumber(number: Int) {
print(number)
}
printNumber(number: number)
```
在此示例中,`anotherNumber` 是 `number` 的引用。对 `anotherNumber` 所做的任何更改都会影响 `number`。
## 总结
在 Swift 中,引用类型包括类、闭包、泛型类型和可变类型。引用类型是通过引用传递的,这意味着当您将引用类型赋值给另一个变量或将其作为参数传递给函数时,将创建对该类型的引用。
* 类
* 闭包
* 泛型类型
* 可变类型,例如 `var` 和 `let`
## 类
类是引用类型,因为它们是通过引用传递的。这意味着当您将类实例赋值给另一个变量或将其作为参数传递给函数时,将创建对该实例的引用。
以下是一些示例:
```swift
// 创建一个类实例
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
var person1 = Person(name: "John Doe", age: 30)
// 将类实例赋值给另一个变量
var person2 = person1
// 将类实例作为参数传递给函数
func printPerson(person: Person) {
print(person.name)
print(person.age)
}
printPerson(person: person1)
```
在此示例中,`person2` 是 `person1` 的引用。对 `person2` 所做的任何更改都会影响 `person1`。
## 闭包
闭包是引用类型,因为它们是通过引用传递的。这意味着当您将闭包赋值给另一个变量或将其作为参数传递给函数时,将创建对该闭包的引用。
以下是一些示例:
```swift
// 创建一个闭包
let closure = { (name: String) -> String in
return "Hello, \(name)!"
}
// 将闭包赋值给另一个变量
let anotherClosure = closure
// 将闭包作为参数传递给函数
func printGreeting(closure: (String) -> String) {
let greeting = closure("John Doe")
print(greeting)
}
printGreeting(closure: closure)
```
在此示例中,`anotherClosure` 是 `closure` 的引用。对 `anotherClosure` 所做的任何更改都会影响 `closure`。
## 泛型类型
泛型类型是引用类型,因为它们是通过引用传递的。这意味着当您将泛型类型实例赋值给另一个变量或将其作为参数传递给函数时,将创建对该实例的引用。
以下是一些示例:
```swift
// 创建一个泛型类型实例
struct Array<T> {
var elements: [T]
init(elements: [T]) {
self.elements = elements
}
}
var array1 = Array(elements: [1, 2, 3])
// 将泛型类型实例赋值给另一个变量
var array2 = array1
// 将泛型类型实例作为参数传递给函数
func printArray(array: Array<Int>) {
for element in array.elements {
print(element)
}
}
printArray(array: array1)
```
在此示例中,`array2` 是 `array1` 的引用。对 `array2` 所做的任何更改都会影响 `array1`。
## 可变类型
可变类型是引用类型,因为它们是通过引用传递的。这意味着当您将可变类型赋值给另一个变量或将其作为参数传递给函数时,将创建对该变量的引用。
以下是一些示例:
```swift
// 创建一个可变类型
var number = 1
// 将可变类型赋值给另一个变量
var anotherNumber = number
// 将可变类型作为参数传递给函数
func printNumber(number: Int) {
print(number)
}
printNumber(number: number)
```
在此示例中,`anotherNumber` 是 `number` 的引用。对 `anotherNumber` 所做的任何更改都会影响 `number`。
## 总结
在 Swift 中,引用类型包括类、闭包、泛型类型和可变类型。引用类型是通过引用传递的,这意味着当您将引用类型赋值给另一个变量或将其作为参数传递给函数时,将创建对该类型的引用。
根据类型种类分五类
结构体
类类型
元组类型
区间类型
引用类型
根据是否可赋空分为可选类型和非可选类型
详见05、可选类型
所有类型的类型:泛型类型
泛型本身不是值类型也不是引用类型。
泛型类型可以包含值类型或引用类型。
当包含值类型的泛型类型被赋值给新变量或传递给函数时,其值会被复制。
当包含引用类型的泛型类型被赋值给新变量或传递给函数时,其值会被引用。
泛型类型可以包含值类型或引用类型。
当包含值类型的泛型类型被赋值给新变量或传递给函数时,其值会被复制。
当包含引用类型的泛型类型被赋值给新变量或传递给函数时,其值会被引用。
泛型本身不是值类型也不是引用类型。泛型是一种参数化类型,它可以用于创建支持不同类型参数的类型和函数。
**泛型类型在运行时会被擦除**,这意味着在运行时,泛型类型的信息会被消除,只剩下具体的类型参数。因此,泛型类型不能像值类型或引用类型那样直接进行赋值或传递。
**但是,泛型类型可以包含值类型或引用类型**。例如,`Array<Int>` 是一个泛型类型,它可以包含 `Int` 值类型的数组。`Dictionary<String, String>` 是另一个泛型类型,它可以包含 `String` 键和 `String` 值的字典。
**当包含值类型的泛型类型被赋值给新变量或传递给函数时,其值会被复制**。这是因为值类型是深拷贝的。
**当包含引用类型的泛型类型被赋值给新变量或传递给函数时,其值会被引用**。这是因为引用类型是浅拷贝的。
**以下示例说明了泛型类型包含值类型和引用类型的情况:**
```swift
// 泛型类型包含值类型
struct Point<T> {
var x: T
var y: T
}
let point1 = Point(x: 1, y: 2)
var point2 = point1
// 修改 point2 的值不会影响 point1 的值
point2.x = 3
print(point1.x) // prints "1"
print(point2.x) // prints "3"
// 泛型类型包含引用类型
class Person {
var name: String
init(name: String) {
self.name = name
}
}
let person1 = Person(name: "John")
var person2 = person1
// 修改 person2 的值会影响 person1 的值
person2.name = "Mary"
print(person1.name) // prints "Mary"
print(person2.name) // prints "Mary"
```
**输出结果表明:**
* 修改 `point2` 的值不会影响 `point1` 的值,因为 `Point<T>` 是包含值类型的泛型类型。
* 修改 `person2` 的值会影响 `person1` 的值,因为 `Person` 是引用类型。
**总结:**
* 泛型本身不是值类型也不是引用类型。
* 泛型类型可以包含值类型或引用类型。
* 当包含值类型的泛型类型被赋值给新变量或传递给函数时,其值会被复制。
* 当包含引用类型的泛型类型被赋值给新变量或传递给函数时,其值会被引用。
理解泛型类型和值类型、引用类型的区别可以帮助您编写更优雅、更高效、更安全的 Swift 代码。
**泛型类型在运行时会被擦除**,这意味着在运行时,泛型类型的信息会被消除,只剩下具体的类型参数。因此,泛型类型不能像值类型或引用类型那样直接进行赋值或传递。
**但是,泛型类型可以包含值类型或引用类型**。例如,`Array<Int>` 是一个泛型类型,它可以包含 `Int` 值类型的数组。`Dictionary<String, String>` 是另一个泛型类型,它可以包含 `String` 键和 `String` 值的字典。
**当包含值类型的泛型类型被赋值给新变量或传递给函数时,其值会被复制**。这是因为值类型是深拷贝的。
**当包含引用类型的泛型类型被赋值给新变量或传递给函数时,其值会被引用**。这是因为引用类型是浅拷贝的。
**以下示例说明了泛型类型包含值类型和引用类型的情况:**
```swift
// 泛型类型包含值类型
struct Point<T> {
var x: T
var y: T
}
let point1 = Point(x: 1, y: 2)
var point2 = point1
// 修改 point2 的值不会影响 point1 的值
point2.x = 3
print(point1.x) // prints "1"
print(point2.x) // prints "3"
// 泛型类型包含引用类型
class Person {
var name: String
init(name: String) {
self.name = name
}
}
let person1 = Person(name: "John")
var person2 = person1
// 修改 person2 的值会影响 person1 的值
person2.name = "Mary"
print(person1.name) // prints "Mary"
print(person2.name) // prints "Mary"
```
**输出结果表明:**
* 修改 `point2` 的值不会影响 `point1` 的值,因为 `Point<T>` 是包含值类型的泛型类型。
* 修改 `person2` 的值会影响 `person1` 的值,因为 `Person` 是引用类型。
**总结:**
* 泛型本身不是值类型也不是引用类型。
* 泛型类型可以包含值类型或引用类型。
* 当包含值类型的泛型类型被赋值给新变量或传递给函数时,其值会被复制。
* 当包含引用类型的泛型类型被赋值给新变量或传递给函数时,其值会被引用。
理解泛型类型和值类型、引用类型的区别可以帮助您编写更优雅、更高效、更安全的 Swift 代码。
类型转换
什么情况下需要强制类型转换
范围大的类型转换为范围小的类型,比如将 `Double` 转换为 `Int`
是什么
类型转换是指将一种类型的值转换为另一种类型的值的操作。类型转换可以分为隐式类型转换和显式类型转换。
**隐式类型转换**
隐式类型转换是指编译器自动执行的类型转换。在大多数情况下,隐式类型转换是安全的,因为编译器会确保转换后的值不会丢失精度或导致数据溢出。
以下是一些常见的隐式类型转换:
* 将较小的整数类型转换为较大的整数类型,例如将 `Int8` 转换为 `Int32`。
* 将浮点数类型转换为整数类型,例如将 `Int` 转换为 `Double`。
* 将字符类型转换为整数类型,例如将 `Character` 转换为 `UInt8`。
**显式类型转换**
显式类型转换是指程序员使用强制类型转换运算符(`as` 或 `as?`)来指定类型的转换。显式类型转换可以用于将一种类型的值转换为另一种类型,即使转换可能导致数据丢失或溢出。
以下是一些常见的显式类型转换:
* 将 `Int` 类型的值转换为 `String` 类型,例如:
```swift
let number = 10
let string = String(number)
```
* 将 `String` 类型的值转换为 `Int` 类型,例如:
```swift
let string = "10"
let number = Int(string)
```
**类型转换的注意事项**
* 在使用隐式类型转换时,需要注意转换后的值是否会丢失精度或导致数据溢出。
* 在使用显式类型转换时,需要注意转换是否安全,是否会导致数据丢失或溢出。
* 在使用类型转换时,需要考虑代码的可读性和可维护性。
**以下是一些使用类型转换的示例:**
```swift
// 将 Int 类型的值转换为 String 类型
let number = 10
let string = String(number)
// 将 String 类型的值转换为 Int 类型
let string = "10"
let number = Int(string)
// 将 Double 类型的值转换为 Int 类型
let doubleValue = 3.14
let intValue = Int(doubleValue) // 结果为 3
// 将 Int 类型的值转换为 Double 类型
let intValue = 10
let doubleValue = Double(intValue) // 结果为 10.0
// 使用 as? 进行类型转换
let string = "123"
let number = Int(string) // 隐式类型转换,结果为 123
let string2 = "abc"
let number2 = Int(string2) // 隐式类型转换,结果为 nil
let number3 = string2 as? Int // 显式类型转换,结果为 nil
```
**总结:**
* 类型转换是将一种类型的值转换为另一种类型的值的操作。
* 类型转换可以分为隐式类型转换和显式类型转换。
* 在使用类型转换时,需要注意转换是否安全,是否会导致数据丢失或溢出。
* 理解类型转换可以帮助您编写更优雅、更高效、更安全的 Swift 代码。
**隐式类型转换**
隐式类型转换是指编译器自动执行的类型转换。在大多数情况下,隐式类型转换是安全的,因为编译器会确保转换后的值不会丢失精度或导致数据溢出。
以下是一些常见的隐式类型转换:
* 将较小的整数类型转换为较大的整数类型,例如将 `Int8` 转换为 `Int32`。
* 将浮点数类型转换为整数类型,例如将 `Int` 转换为 `Double`。
* 将字符类型转换为整数类型,例如将 `Character` 转换为 `UInt8`。
**显式类型转换**
显式类型转换是指程序员使用强制类型转换运算符(`as` 或 `as?`)来指定类型的转换。显式类型转换可以用于将一种类型的值转换为另一种类型,即使转换可能导致数据丢失或溢出。
以下是一些常见的显式类型转换:
* 将 `Int` 类型的值转换为 `String` 类型,例如:
```swift
let number = 10
let string = String(number)
```
* 将 `String` 类型的值转换为 `Int` 类型,例如:
```swift
let string = "10"
let number = Int(string)
```
**类型转换的注意事项**
* 在使用隐式类型转换时,需要注意转换后的值是否会丢失精度或导致数据溢出。
* 在使用显式类型转换时,需要注意转换是否安全,是否会导致数据丢失或溢出。
* 在使用类型转换时,需要考虑代码的可读性和可维护性。
**以下是一些使用类型转换的示例:**
```swift
// 将 Int 类型的值转换为 String 类型
let number = 10
let string = String(number)
// 将 String 类型的值转换为 Int 类型
let string = "10"
let number = Int(string)
// 将 Double 类型的值转换为 Int 类型
let doubleValue = 3.14
let intValue = Int(doubleValue) // 结果为 3
// 将 Int 类型的值转换为 Double 类型
let intValue = 10
let doubleValue = Double(intValue) // 结果为 10.0
// 使用 as? 进行类型转换
let string = "123"
let number = Int(string) // 隐式类型转换,结果为 123
let string2 = "abc"
let number2 = Int(string2) // 隐式类型转换,结果为 nil
let number3 = string2 as? Int // 显式类型转换,结果为 nil
```
**总结:**
* 类型转换是将一种类型的值转换为另一种类型的值的操作。
* 类型转换可以分为隐式类型转换和显式类型转换。
* 在使用类型转换时,需要注意转换是否安全,是否会导致数据丢失或溢出。
* 理解类型转换可以帮助您编写更优雅、更高效、更安全的 Swift 代码。
类型转换的五种方法
```在 Swift 中,将 Double 转换为 Int 有五种常见的方法:as和as?、构造方法、数学函数、自定义函数以及第三方库```
**1. 使用强制类型转换运算符 `as` 或 `as?`**
* `as` 运算符会将 Double 值转换为 Int 值,如果转换失败,则结果为 nil。
* `as?` 运算符会将 Double 值转换为 Int 值,如果转换失败,则结果为 nil。
**示例:**
```swift
let doubleValue: Double = 3.14
// 使用 as 运算符
let intValue = doubleValue as Int // 结果为 3
// 使用 as? 运算符
let intValue2 = doubleValue as? Int // 结果为 3
let doubleValue2: Double = "abc"
// 使用 as 运算符
let intValue3 = doubleValue2 as Int // 结果为 nil
// 使用 as? 运算符
let intValue4 = doubleValue2 as? Int // 结果为 nil
```
**2. 使用初始化器**
* 可以使用 Double 的初始化器将 Int 值转换为 Double 值。
示例:
```
Swift
let intValue: Int = 10
// 使用 Double 的初始化器
let doubleValue5 = Double(intValue) // 结果为 10.0
```
**3. 使用数学函数**
* 使用 `floor()` 函数可以将 Double 值向下取整为 Int 值。
* 使用 `ceil()` 函数可以将 Double 值向上取整为 Int 值。
* 使用 `round()` 函数可以将 Double 值四舍五入为 Int 值。
**示例:**
```swift
let doubleValue: Double = 3.14
// 使用 floor() 函数
let intValue5 = floor(doubleValue) // 结果为 3
// 使用 ceil() 函数
let intValue6 = ceil(doubleValue) // 结果为 4
// 使用 round() 函数
let intValue7 = round(doubleValue) // 结果为 3
```
**4. 使用自定义函数**
您可以定义一个自定义函数来执行 Double 到 Int 的转换。例如:
```swift
func doubleToInt(_ doubleValue: Double) -> Int {
return Int(floor(doubleValue))
}
let doubleValue = 3.14
// 使用自定义函数
let intValue8 = doubleToInt(doubleValue) // 结果为 3
```
**5. 使用第三方库**
您可以使用第三方库来执行 Double 到 Int 的转换。例如,`Numeric` 库提供了一个 `toInt()` 方法可以将 Double 值转换为 Int 值。
```swift
import Numeric
let doubleValue = 3.14
// 使用第三方库
let intValue9 = doubleValue.toInt() // 结果为 3
```
**选择哪种方法取决于您的具体需求:**
* 如果您需要确保转换成功,请使用 `as` 运算符。
* 如果您需要处理转换失败的情况,请使用 `as?` 运算符。
* 如果您需要对 Double 值进行取整或四舍五入,请使用相应的数学函数。
* 如果您需要更复杂的转换逻辑,可以定义自定义函数。
* 如果您需要使用第三方库提供的功能,可以使用第三方库。
**总结:**
* 在 Swift 中,将 Double 转换为 Int 有多种方法。
* 选择哪种方法取决于您的具体需求。
* 理解如何将 Double 转换为 Int 可以帮助您编写更优雅、更高效、更安全的 Swift 代码。
**注意:**
* 将 Double 转换为 Int 时可能会丢失精度。
* 如果 Double 值太大或太小,转换可能会导致溢出。
```在 Swift 中,将 Double 转换为 Int 有五种常见的方法:as和as?、构造方法、数学函数、自定义函数以及第三方库```
**1. 使用强制类型转换运算符 `as` 或 `as?`**
* `as` 运算符会将 Double 值转换为 Int 值,如果转换失败,则结果为 nil。
* `as?` 运算符会将 Double 值转换为 Int 值,如果转换失败,则结果为 nil。
**示例:**
```swift
let doubleValue: Double = 3.14
// 使用 as 运算符
let intValue = doubleValue as Int // 结果为 3
// 使用 as? 运算符
let intValue2 = doubleValue as? Int // 结果为 3
let doubleValue2: Double = "abc"
// 使用 as 运算符
let intValue3 = doubleValue2 as Int // 结果为 nil
// 使用 as? 运算符
let intValue4 = doubleValue2 as? Int // 结果为 nil
```
**2. 使用初始化器**
* 可以使用 Double 的初始化器将 Int 值转换为 Double 值。
示例:
```
Swift
let intValue: Int = 10
// 使用 Double 的初始化器
let doubleValue5 = Double(intValue) // 结果为 10.0
```
**3. 使用数学函数**
* 使用 `floor()` 函数可以将 Double 值向下取整为 Int 值。
* 使用 `ceil()` 函数可以将 Double 值向上取整为 Int 值。
* 使用 `round()` 函数可以将 Double 值四舍五入为 Int 值。
**示例:**
```swift
let doubleValue: Double = 3.14
// 使用 floor() 函数
let intValue5 = floor(doubleValue) // 结果为 3
// 使用 ceil() 函数
let intValue6 = ceil(doubleValue) // 结果为 4
// 使用 round() 函数
let intValue7 = round(doubleValue) // 结果为 3
```
**4. 使用自定义函数**
您可以定义一个自定义函数来执行 Double 到 Int 的转换。例如:
```swift
func doubleToInt(_ doubleValue: Double) -> Int {
return Int(floor(doubleValue))
}
let doubleValue = 3.14
// 使用自定义函数
let intValue8 = doubleToInt(doubleValue) // 结果为 3
```
**5. 使用第三方库**
您可以使用第三方库来执行 Double 到 Int 的转换。例如,`Numeric` 库提供了一个 `toInt()` 方法可以将 Double 值转换为 Int 值。
```swift
import Numeric
let doubleValue = 3.14
// 使用第三方库
let intValue9 = doubleValue.toInt() // 结果为 3
```
**选择哪种方法取决于您的具体需求:**
* 如果您需要确保转换成功,请使用 `as` 运算符。
* 如果您需要处理转换失败的情况,请使用 `as?` 运算符。
* 如果您需要对 Double 值进行取整或四舍五入,请使用相应的数学函数。
* 如果您需要更复杂的转换逻辑,可以定义自定义函数。
* 如果您需要使用第三方库提供的功能,可以使用第三方库。
**总结:**
* 在 Swift 中,将 Double 转换为 Int 有多种方法。
* 选择哪种方法取决于您的具体需求。
* 理解如何将 Double 转换为 Int 可以帮助您编写更优雅、更高效、更安全的 Swift 代码。
**注意:**
* 将 Double 转换为 Int 时可能会丢失精度。
* 如果 Double 值太大或太小,转换可能会导致溢出。
题:
问:值类型和引用类型各自的优缺点,以及他们的区别是什么
答:## 值类型和引用类型
在 Swift 中,类型可以分为值类型和引用类型。
### 值类型
**特点:**
* **独立性:** 值类型的每个实例都拥有自己的独立数据副本。
* **深拷贝:** 当值类型被赋值给新变量或传递给函数时,其值会被深拷贝,即新变量或函数参数将获得该值的独立副本。
* **线程安全性:** 值类型在多线程环境下是安全的,因为每个线程都操作的是该值的独立副本。
**优势:**
* **性能:** 值类型的赋值和传递操作效率更高,因为不需要额外的引用计数和内存管理。
* **内存安全性:** 值类型避免了引用类型常见的循环引用问题,可以减少内存泄漏的风险。
* **易用性:** 值类型的语义更加直观易懂,可以使代码更易于理解和维护。
**劣势:**
* **内存占用:** 值类型在某些情况下可能占用更多的内存,因为每个实例都拥有自己的独立数据副本。
* **性能开销:** 值类型的深拷贝操作在某些情况下可能带来性能开销。
**示例:**
* 基本类型:`Int`、`Double`、`Float`、`String`、`Bool`
* 结构体(`struct`)
* 枚举(`enum`)
* 元组(`tuple`)
### 引用类型
**特点:**
* **共享性:** 引用类型的多个实例可以共享同一个数据副本。
* **浅拷贝:** 当引用类型被赋值给新变量或传递给函数时,其值会被浅拷贝,即新变量或函数参数将获得对同一个数据副本的引用。
* **线程不安全性:** 引用类型在多线程环境下可能存在线程安全问题,因为多个线程可能操作同一个数据副本。
**优势:**
* **内存占用:** 引用类型在某些情况下可以节省内存,因为多个实例可以共享同一个数据副本。
* **性能优化:** 引用类型的浅拷贝操作在某些情况下可以提高性能。
**劣势:**
* **内存泄漏:** 引用类型容易出现循环引用问题,导致内存泄漏。
* **线程安全性:** 引用类型在多线程环境下需要额外的同步机制来保证线程安全性。
* **复杂性:** 引用类型的语义相对复杂,可能使代码更难理解和维护。
**示例:**
* 类(`class`)
* 闭包(`closure`)
### 区别
| 特性 | 值类型 | 引用类型 |
|---|---|---|
| 数据存储 | 独立数据副本 | 共享数据副本 |
| 赋值方式 | 深拷贝 | 浅拷贝 |
| 线程安全性 | 安全 | 不安全 |
| 内存占用 | 较高 | 较低 |
| 性能 | 较高 | 较低 |
| 易用性 | 易用 | 复杂 |
| 内存泄漏风险 | 低 | 高 |
| 示例 | `Int`、`String`、`struct` | `class`、`closure` |
**总结:**
* 值类型和引用类型是 Swift 中两种重要的类型。
* 它们在数据存储、赋值方式、线程安全性、内存占用、性能、易用性、内存泄漏风险等方面存在差异。
* 选择合适的类型可以提高代码的性能、安全性、可维护性和可读性。
**建议:**
* 首选值类型,除非有明确的理由使用引用类型。
* 使用引用类型时,需要谨慎考虑线程安全问题和内存泄漏风险。
在 Swift 中,类型可以分为值类型和引用类型。
### 值类型
**特点:**
* **独立性:** 值类型的每个实例都拥有自己的独立数据副本。
* **深拷贝:** 当值类型被赋值给新变量或传递给函数时,其值会被深拷贝,即新变量或函数参数将获得该值的独立副本。
* **线程安全性:** 值类型在多线程环境下是安全的,因为每个线程都操作的是该值的独立副本。
**优势:**
* **性能:** 值类型的赋值和传递操作效率更高,因为不需要额外的引用计数和内存管理。
* **内存安全性:** 值类型避免了引用类型常见的循环引用问题,可以减少内存泄漏的风险。
* **易用性:** 值类型的语义更加直观易懂,可以使代码更易于理解和维护。
**劣势:**
* **内存占用:** 值类型在某些情况下可能占用更多的内存,因为每个实例都拥有自己的独立数据副本。
* **性能开销:** 值类型的深拷贝操作在某些情况下可能带来性能开销。
**示例:**
* 基本类型:`Int`、`Double`、`Float`、`String`、`Bool`
* 结构体(`struct`)
* 枚举(`enum`)
* 元组(`tuple`)
### 引用类型
**特点:**
* **共享性:** 引用类型的多个实例可以共享同一个数据副本。
* **浅拷贝:** 当引用类型被赋值给新变量或传递给函数时,其值会被浅拷贝,即新变量或函数参数将获得对同一个数据副本的引用。
* **线程不安全性:** 引用类型在多线程环境下可能存在线程安全问题,因为多个线程可能操作同一个数据副本。
**优势:**
* **内存占用:** 引用类型在某些情况下可以节省内存,因为多个实例可以共享同一个数据副本。
* **性能优化:** 引用类型的浅拷贝操作在某些情况下可以提高性能。
**劣势:**
* **内存泄漏:** 引用类型容易出现循环引用问题,导致内存泄漏。
* **线程安全性:** 引用类型在多线程环境下需要额外的同步机制来保证线程安全性。
* **复杂性:** 引用类型的语义相对复杂,可能使代码更难理解和维护。
**示例:**
* 类(`class`)
* 闭包(`closure`)
### 区别
| 特性 | 值类型 | 引用类型 |
|---|---|---|
| 数据存储 | 独立数据副本 | 共享数据副本 |
| 赋值方式 | 深拷贝 | 浅拷贝 |
| 线程安全性 | 安全 | 不安全 |
| 内存占用 | 较高 | 较低 |
| 性能 | 较高 | 较低 |
| 易用性 | 易用 | 复杂 |
| 内存泄漏风险 | 低 | 高 |
| 示例 | `Int`、`String`、`struct` | `class`、`closure` |
**总结:**
* 值类型和引用类型是 Swift 中两种重要的类型。
* 它们在数据存储、赋值方式、线程安全性、内存占用、性能、易用性、内存泄漏风险等方面存在差异。
* 选择合适的类型可以提高代码的性能、安全性、可维护性和可读性。
**建议:**
* 首选值类型,除非有明确的理由使用引用类型。
* 使用引用类型时,需要谨慎考虑线程安全问题和内存泄漏风险。
问:值类型一定存储在栈上,引用类型一定存储在堆上吗
总结:一般情况下,值类型存储在栈上,而引用类型存储在堆上,但并不是绝对的规则。在编译器和运行时的优化过程中,可能会对存储位置进行优化或变化。
不完全正确。在 Swift 中,值类型和引用类型的存储位置并不是一定的,而是取决于它们的具体情况和上下文。
1. **值类型(Value Types)**:
- 值类型包括结构体(Structures)、枚举(Enumerations)和基本数据类型(比如整数、浮点数、布尔值等)。
- 值类型通常存储在栈上,但在某些情况下也可能存储在堆上,比如在闭包中捕获值类型的情况下,或者在进行了值类型的拷贝优化时。
- 栈上的存储使得值类型的内存管理更加高效,因为它们的生命周期是与其所在的作用域密切相关的。
2. **引用类型(Reference Types)**:
- 引用类型包括类(Class)和闭包(Closure)。
- 引用类型通常存储在堆上。这是因为它们的内存分配和释放由引用计数(Reference Counting)来管理,以确保内存的正确释放。
- 当我们创建一个类的实例时,实例本身存储在堆上,而栈上存储的是对实例的引用。
不完全正确。在 Swift 中,值类型和引用类型的存储位置并不是一定的,而是取决于它们的具体情况和上下文。
1. **值类型(Value Types)**:
- 值类型包括结构体(Structures)、枚举(Enumerations)和基本数据类型(比如整数、浮点数、布尔值等)。
- 值类型通常存储在栈上,但在某些情况下也可能存储在堆上,比如在闭包中捕获值类型的情况下,或者在进行了值类型的拷贝优化时。
- 栈上的存储使得值类型的内存管理更加高效,因为它们的生命周期是与其所在的作用域密切相关的。
2. **引用类型(Reference Types)**:
- 引用类型包括类(Class)和闭包(Closure)。
- 引用类型通常存储在堆上。这是因为它们的内存分配和释放由引用计数(Reference Counting)来管理,以确保内存的正确释放。
- 当我们创建一个类的实例时,实例本身存储在堆上,而栈上存储的是对实例的引用。
符
标点符号
02、控制流程:wrifs
if-else
在 Swift 中,`if-else` 是用于进行条件判断的基本语句。下面是 `if-else` 的基本使用方法:
```swift
let number = 10
if number > 0 {
print("Number is positive")
} else if number < 0 {
print("Number is negative")
} else {
print("Number is zero")
}
```
在上面的例子中,首先定义了一个常量 `number`,然后使用 `if-else` 结构来检查 `number` 的值。如果 `number` 大于 0,则打印 `"Number is positive"`;如果 `number` 小于 0,则打印 `"Number is negative"`;如果 `number` 等于 0,则打印 `"Number is zero"`。
此外,`if-else` 结构也可以嵌套使用,例如:
```swift
let a = 10
let b = 20
if a > 0 {
if b > 0 {
print("Both a and b are positive")
} else {
print("a is positive but b is not")
}
} else {
print("a is not positive")
}
```
在这个例子中,我们检查了两个变量 `a` 和 `b` 的值。如果 `a` 大于 0,则进一步检查 `b` 的值,如果 `b` 也大于 0,则打印 `"Both a and b are positive"`;如果 `b` 不大于 0,则打印 `"a is positive but b is not"`。如果 `a` 不大于 0,则直接打印 `"a is not positive"`。
这就是 Swift 中使用 `if-else` 进行条件判断的基本方法。
```swift
let number = 10
if number > 0 {
print("Number is positive")
} else if number < 0 {
print("Number is negative")
} else {
print("Number is zero")
}
```
在上面的例子中,首先定义了一个常量 `number`,然后使用 `if-else` 结构来检查 `number` 的值。如果 `number` 大于 0,则打印 `"Number is positive"`;如果 `number` 小于 0,则打印 `"Number is negative"`;如果 `number` 等于 0,则打印 `"Number is zero"`。
此外,`if-else` 结构也可以嵌套使用,例如:
```swift
let a = 10
let b = 20
if a > 0 {
if b > 0 {
print("Both a and b are positive")
} else {
print("a is positive but b is not")
}
} else {
print("a is not positive")
}
```
在这个例子中,我们检查了两个变量 `a` 和 `b` 的值。如果 `a` 大于 0,则进一步检查 `b` 的值,如果 `b` 也大于 0,则打印 `"Both a and b are positive"`;如果 `b` 不大于 0,则打印 `"a is positive but b is not"`。如果 `a` 不大于 0,则直接打印 `"a is not positive"`。
这就是 Swift 中使用 `if-else` 进行条件判断的基本方法。
while
在 Swift 中,`while` 是一种循环语句,用于重复执行一段代码,直到指定的条件不再满足为止。`while` 循环会在每次循环开始之前检查条件是否为真,如果条件为真,则执行循环体中的代码;如果条件为假,则退出循环。
`while` 循环的基本语法如下所示:
```swift
while condition {
// 循环体
}
```
在这个语法中,`condition` 是一个布尔表达式,表示循环的条件。只要 `condition` 为真,循环就会继续执行;一旦 `condition` 为假,循环就会终止,程序会继续执行循环后的代码。
下面是一个简单的示例,演示了如何使用 `while` 循环计算从 1 到 5 的数字的和:
```swift
var sum = 0
var number = 1
while number <= 5 {
sum += number
number += 1
}
print("Sum is \(sum)")
```
在这个示例中,`while` 循环会在 `number` 的值小于或等于 5 的情况下重复执行循环体。在每次循环中,将 `number` 添加到 `sum` 中,并递增 `number` 的值。当 `number` 的值超过 5 时,循环终止,最后打印出计算得到的和。
`while` 循环是 Swift 中常用的控制流语句之一,它提供了一种灵活的方式来重复执行代码,直到特定条件不再满足为止。
`while` 循环的基本语法如下所示:
```swift
while condition {
// 循环体
}
```
在这个语法中,`condition` 是一个布尔表达式,表示循环的条件。只要 `condition` 为真,循环就会继续执行;一旦 `condition` 为假,循环就会终止,程序会继续执行循环后的代码。
下面是一个简单的示例,演示了如何使用 `while` 循环计算从 1 到 5 的数字的和:
```swift
var sum = 0
var number = 1
while number <= 5 {
sum += number
number += 1
}
print("Sum is \(sum)")
```
在这个示例中,`while` 循环会在 `number` 的值小于或等于 5 的情况下重复执行循环体。在每次循环中,将 `number` 添加到 `sum` 中,并递增 `number` 的值。当 `number` 的值超过 5 时,循环终止,最后打印出计算得到的和。
`while` 循环是 Swift 中常用的控制流语句之一,它提供了一种灵活的方式来重复执行代码,直到特定条件不再满足为止。
repeat-while
相当于c语言的do-while
for
++for
for-in
例子
遍历数组元素
for name in names[0...3] {}
闭区间
[0...3]
0、1、2、3
半开区间
[0..<3]
0、1、2
遍历整型区间
for i in 0...3 { // i = 0\1\2\3}
闭区间
0...3
半开区间
0..<3
switch-case
改进
支持多类型比较、值绑定、条件判断
和oc的差异与增强
没有默认穿透,不可以使用默认穿透,如果需要穿透可以使用fallthrough
break可以省略
case 符合条件
如 case "a", "A":
不仅支持整形,也支持字符、字符串、区间、元组匹配
这个强大了尤其是配合元组,无敌了简直
支持值绑定
举例
let point = (2,0) case (let x , 0) : x is binded to 2, and you can use it at here. break
必要时let也可以改为var
支持条件判断
元组+值绑定+条件判断,这个厉害了
举例
case (x,y) where x = -y: //here is run when x is equal to -y. break
default是非必须的
must exhaustive. 保证switch处理了所有的情况
case、default后面至少要有一条语句,可以是break,因此使用不了默认穿透。switch没有默认穿透。
标签语句
类似goto
举例参考02-流程控制
03、函数
函数的定义
在Swift中,函数的定义遵循一般的语法规则。下面是一个简单的函数定义示例:
```swift
func greet(name: String) -> String {
return "Hello, \(name)!"
}
```
在这个例子中,我们定义了一个名为 `greet` 的函数,它接受一个名为 `name` 的参数(参数类型为 `String`),并返回一个 `String` 类型的值。函数体内部使用字符串插值来构造返回的问候语。
这是函数定义的基本结构:
1. `func` 关键字:用于声明一个函数。
2. 函数名:函数的名称,以标识符的形式命名,按照通用的Swift命名规范。
3. 参数列表:参数是一对括号 `()` 中的内容,可以包含零个或多个参数,每个参数由参数名和参数类型组成,用冒号 `:` 分隔。在示例中,参数为 `name: String`,表示一个名为 `name` 的参数,其类型为 `String`。
4. 返回箭头 `->`:用于指定函数返回的类型。在示例中,返回箭头后面的 `String` 表示该函数返回一个 `String` 类型的值。
5. 函数体:花括号 `{}` 内的代码块是函数的实际实现,包含了函数执行的具体逻辑。
你可以调用这个函数来使用它,比如:
```swift
let greeting = greet(name: "John")
print(greeting) // 输出: Hello, John!
```
这个例子中,我们调用了 `greet` 函数并传入了一个参数 `"John"`,然后将返回的问候语存储在变量 `greeting` 中,并将其打印到控制台上。
```swift
func greet(name: String) -> String {
return "Hello, \(name)!"
}
```
在这个例子中,我们定义了一个名为 `greet` 的函数,它接受一个名为 `name` 的参数(参数类型为 `String`),并返回一个 `String` 类型的值。函数体内部使用字符串插值来构造返回的问候语。
这是函数定义的基本结构:
1. `func` 关键字:用于声明一个函数。
2. 函数名:函数的名称,以标识符的形式命名,按照通用的Swift命名规范。
3. 参数列表:参数是一对括号 `()` 中的内容,可以包含零个或多个参数,每个参数由参数名和参数类型组成,用冒号 `:` 分隔。在示例中,参数为 `name: String`,表示一个名为 `name` 的参数,其类型为 `String`。
4. 返回箭头 `->`:用于指定函数返回的类型。在示例中,返回箭头后面的 `String` 表示该函数返回一个 `String` 类型的值。
5. 函数体:花括号 `{}` 内的代码块是函数的实际实现,包含了函数执行的具体逻辑。
你可以调用这个函数来使用它,比如:
```swift
let greeting = greet(name: "John")
print(greeting) // 输出: Hello, John!
```
这个例子中,我们调用了 `greet` 函数并传入了一个参数 `"John"`,然后将返回的问候语存储在变量 `greeting` 中,并将其打印到控制台上。
隐式返回
在Swift中,如果函数体只有一行表达式,并且返回类型可以通过表达式的类型推断出来,你可以使用隐式返回。这意味着你可以省略 `return` 关键字,而直接将表达式写在函数体内。下面是一个使用隐式返回的例子:
```swift
func double(_ number: Int) -> Int {
number * 2
}
```
在这个例子中,我们定义了一个名为 `double` 的函数,它接受一个整数参数 `number`,并返回这个参数的两倍。函数体只有一行表达式 `number * 2`,因此可以省略 `return` 关键字。
你可以像平常一样调用这个函数:
```swift
let result = double(5)
print(result) // 输出: 10
```
这里的 `result` 将会是 `10`,因为 `double(5)` 返回 `5` 的两倍,即 `10`。
```swift
func double(_ number: Int) -> Int {
number * 2
}
```
在这个例子中,我们定义了一个名为 `double` 的函数,它接受一个整数参数 `number`,并返回这个参数的两倍。函数体只有一行表达式 `number * 2`,因此可以省略 `return` 关键字。
你可以像平常一样调用这个函数:
```swift
let result = double(5)
print(result) // 输出: 10
```
这里的 `result` 将会是 `10`,因为 `double(5)` 返回 `5` 的两倍,即 `10`。
返回元组:实现多返回值(这个爽的一笔)
在Swift中,你可以使用元组来实现多返回值的功能。元组是将多个值组合成单个复合值的一种方式。以下是一个使用元组返回多个值的简单示例:
```swift
func getCoordinates() -> (Double, Double) {
let latitude = 40.7128
let longitude = -74.0060
return (latitude, longitude)
}
```
在这个示例中,`getCoordinates` 函数返回了一个包含两个 `Double` 类型值的元组,表示经度和纬度。你可以这样使用这个函数:
```swift
let coordinates = getCoordinates()
print("Latitude: \(coordinates.0), Longitude: \(coordinates.1)")
```
另一种更清晰的方式是,你可以在调用函数时使用元组分解来获取返回的多个值:
```swift
let (latitude, longitude) = getCoordinates()
print("Latitude: \(latitude), Longitude: \(longitude)")
```
这样你就可以更方便地使用 `latitude` 和 `longitude` 变量,而不需要使用元组的索引。
```swift
func getCoordinates() -> (Double, Double) {
let latitude = 40.7128
let longitude = -74.0060
return (latitude, longitude)
}
```
在这个示例中,`getCoordinates` 函数返回了一个包含两个 `Double` 类型值的元组,表示经度和纬度。你可以这样使用这个函数:
```swift
let coordinates = getCoordinates()
print("Latitude: \(coordinates.0), Longitude: \(coordinates.1)")
```
另一种更清晰的方式是,你可以在调用函数时使用元组分解来获取返回的多个值:
```swift
let (latitude, longitude) = getCoordinates()
print("Latitude: \(latitude), Longitude: \(longitude)")
```
这样你就可以更方便地使用 `latitude` 和 `longitude` 变量,而不需要使用元组的索引。
函数的文档注释
在Swift中,你可以使用特殊的注释格式来编写函数的文档注释。这些注释以 `///` 开头,并紧跟着描述函数的内容、参数、返回值和可能的异常情况。下面是一个函数文档注释的示例:
```swift
/// 这个函数将两个整数相加并返回结果。
///
/// - Parameters:
/// - a: 第一个整数。
/// - b: 第二个整数。
/// - Returns: 两个整数的和。
func add(a: Int, b: Int) -> Int {
return a + b
}
```
在这个示例中,文档注释首先描述了函数的作用,然后使用 `- Parameters:` 来列出函数的参数及其描述,使用 `- Returns:` 来描述函数的返回值。你还可以使用 `- Throws:` 来描述函数可能抛出的异常情况。如果函数不会抛出异常,可以省略这一部分。
在编写文档注释时,可以使用 Markdown 格式来使注释更具可读性和格式化。例如,你可以使用列表、加粗、斜体等来突出重点或提供更详细的说明。
在Xcode或其他支持Swift开发的集成开发环境中,当你在编写代码时,文档注释通常会自动显示为函数的文档提示,使得其他开发者能够更方便地理解你的代码并使用你编写的函数。
```swift
/// 这个函数将两个整数相加并返回结果。
///
/// - Parameters:
/// - a: 第一个整数。
/// - b: 第二个整数。
/// - Returns: 两个整数的和。
func add(a: Int, b: Int) -> Int {
return a + b
}
```
在这个示例中,文档注释首先描述了函数的作用,然后使用 `- Parameters:` 来列出函数的参数及其描述,使用 `- Returns:` 来描述函数的返回值。你还可以使用 `- Throws:` 来描述函数可能抛出的异常情况。如果函数不会抛出异常,可以省略这一部分。
在编写文档注释时,可以使用 Markdown 格式来使注释更具可读性和格式化。例如,你可以使用列表、加粗、斜体等来突出重点或提供更详细的说明。
在Xcode或其他支持Swift开发的集成开发环境中,当你在编写代码时,文档注释通常会自动显示为函数的文档提示,使得其他开发者能够更方便地理解你的代码并使用你编写的函数。
参数的标签
可以省略,使用_代替
在Swift中,函数参数可以有两种类型的标签:参数名和参数标签(也称为外部参数名)。参数名用于在函数内部引用参数,而参数标签用于在调用函数时标识传递给函数的参数。下面是一个示例:
```swift
func greet(person name: String, from hometown: String) {
print("Hello, \(name)! Glad you could visit from \(hometown).")
}
```
在这个例子中,`person` 和 `from` 是参数标签,`name` 和 `hometown` 是参数名。在函数内部,你可以使用 `name` 和 `hometown` 来引用这两个参数,而在调用函数时,需要使用参数标签来传递参数值:
```swift
greet(person: "Alice", from: "New York")
```
在调用 `greet` 函数时,我们使用了参数标签 `person` 和 `from` 来指定传递给函数的参数值。
如果你希望函数在调用时不需要指定参数标签,你可以使用下划线 `_` 作为参数标签,这样在调用函数时可以省略参数标签。例如:
```swift
func greet(_ name: String, from hometown: String) {
print("Hello, \(name)! Glad you could visit from \(hometown).")
}
```
调用这个函数时可以像这样:
```swift
greet("Alice", from: "New York")
```
在这个示例中,我们省略了 `name` 参数的参数标签,而 `from` 参数仍然需要指定参数标签。
在Swift中,函数参数可以有两种类型的标签:参数名和参数标签(也称为外部参数名)。参数名用于在函数内部引用参数,而参数标签用于在调用函数时标识传递给函数的参数。下面是一个示例:
```swift
func greet(person name: String, from hometown: String) {
print("Hello, \(name)! Glad you could visit from \(hometown).")
}
```
在这个例子中,`person` 和 `from` 是参数标签,`name` 和 `hometown` 是参数名。在函数内部,你可以使用 `name` 和 `hometown` 来引用这两个参数,而在调用函数时,需要使用参数标签来传递参数值:
```swift
greet(person: "Alice", from: "New York")
```
在调用 `greet` 函数时,我们使用了参数标签 `person` 和 `from` 来指定传递给函数的参数值。
如果你希望函数在调用时不需要指定参数标签,你可以使用下划线 `_` 作为参数标签,这样在调用函数时可以省略参数标签。例如:
```swift
func greet(_ name: String, from hometown: String) {
print("Hello, \(name)! Glad you could visit from \(hometown).")
}
```
调用这个函数时可以像这样:
```swift
greet("Alice", from: "New York")
```
在这个示例中,我们省略了 `name` 参数的参数标签,而 `from` 参数仍然需要指定参数标签。
参数的默认值
在Swift中,你可以为函数的参数提供默认值。这使得在调用函数时,可以选择性地省略一些参数,而使用它们的默认值。下面是一个示例:
```swift
func greet(person: String, from hometown: String = "Unknown") {
print("Hello, \(person)! Glad you could visit from \(hometown).")
}
```
在这个示例中,`hometown` 参数有一个默认值 `"Unknown"`。这意味着在调用 `greet` 函数时,如果不提供 `hometown` 参数,它将默认为 `"Unknown"`。你可以这样调用这个函数:
```swift
greet(person: "Alice", from: "New York")
// 输出: Hello, Alice! Glad you could visit from New York.
greet(person: "Bob")
// 输出: Hello, Bob! Glad you could visit from Unknown.
```
在第一个调用中,我们提供了 `hometown` 参数的值 `"New York"`,所以输出中使用了这个值。在第二个调用中,我们没有提供 `hometown` 参数的值,因此函数使用了默认值 `"Unknown"`。
当为参数提供了默认值时,如果你希望在调用函数时只传递部分参数,并且这些参数位于参数列表的后面,那么你需要使用参数标签来指定参数。例如,在上面的示例中,即使省略了 `hometown` 参数,你仍然需要指定参数标签 `from`。
```swift
func greet(person: String, from hometown: String = "Unknown") {
print("Hello, \(person)! Glad you could visit from \(hometown).")
}
```
在这个示例中,`hometown` 参数有一个默认值 `"Unknown"`。这意味着在调用 `greet` 函数时,如果不提供 `hometown` 参数,它将默认为 `"Unknown"`。你可以这样调用这个函数:
```swift
greet(person: "Alice", from: "New York")
// 输出: Hello, Alice! Glad you could visit from New York.
greet(person: "Bob")
// 输出: Hello, Bob! Glad you could visit from Unknown.
```
在第一个调用中,我们提供了 `hometown` 参数的值 `"New York"`,所以输出中使用了这个值。在第二个调用中,我们没有提供 `hometown` 参数的值,因此函数使用了默认值 `"Unknown"`。
当为参数提供了默认值时,如果你希望在调用函数时只传递部分参数,并且这些参数位于参数列表的后面,那么你需要使用参数标签来指定参数。例如,在上面的示例中,即使省略了 `hometown` 参数,你仍然需要指定参数标签 `from`。
可变参数
在Swift中,你可以定义接受可变数量参数的函数,这些参数在函数内部被表示为数组。这种参数称为可变参数。你可以使用三个点 `...` 来指示函数参数是可变的。以下是一个示例:
```swift
func sum(numbers: Int...) -> Int {
var total = 0
for number in numbers {
total += number
}
return total
}
```
在这个示例中,`sum` 函数接受一个或多个整数作为参数,并返回它们的总和。参数 `numbers` 被定义为可变参数,因此你可以传递任意数量的整数给它。
你可以这样调用这个函数:
```swift
let result1 = sum(numbers: 1, 2, 3, 4, 5)
print(result1) // 输出: 15
let result2 = sum(numbers: 10, 20, 30)
print(result2) // 输出: 60
let result3 = sum()
print(result3) // 输出: 0
```
在调用 `sum` 函数时,你可以传递一个或多个整数作为参数。如果没有提供参数,则函数返回 `0`。
在函数内部,可变参数被当作数组 `numbers`,你可以像处理数组一样使用它,例如使用 `for-in` 循环迭代参数中的每个元素。
```swift
func sum(numbers: Int...) -> Int {
var total = 0
for number in numbers {
total += number
}
return total
}
```
在这个示例中,`sum` 函数接受一个或多个整数作为参数,并返回它们的总和。参数 `numbers` 被定义为可变参数,因此你可以传递任意数量的整数给它。
你可以这样调用这个函数:
```swift
let result1 = sum(numbers: 1, 2, 3, 4, 5)
print(result1) // 输出: 15
let result2 = sum(numbers: 10, 20, 30)
print(result2) // 输出: 60
let result3 = sum()
print(result3) // 输出: 0
```
在调用 `sum` 函数时,你可以传递一个或多个整数作为参数。如果没有提供参数,则函数返回 `0`。
在函数内部,可变参数被当作数组 `numbers`,你可以像处理数组一样使用它,例如使用 `for-in` 循环迭代参数中的每个元素。
输入输出参数
介绍
修饰符 inout
在Swift中,除了常规的函数参数,还可以使用输入输出参数(in-out parameters)。输入输出参数允许函数改变函数体外部变量的值,并将这些更改传递回调用者。使用输入输出参数,你可以在调用函数时直接修改外部变量的值,而不需要通过函数的返回值来实现。
下面是一个使用输入输出参数的简单示例:
```swift
func swapInts(_ a: inout Int, _ b: inout Int) {
let temp = a
a = b
b = temp
}
var x = 5
var y = 10
print("Before swapping: x = \(x), y = \(y)")
swapInts(&x, &y)
print("After swapping: x = \(x), y = \(y)")
```
在这个示例中,我们定义了一个名为 `swapInts` 的函数,它接受两个输入输出参数 `a` 和 `b`,这两个参数被标记为 `inout`。函数内部交换了 `a` 和 `b` 的值。在调用 `swapInts` 函数时,我们必须在参数前面加上 `&` 符号,以指示这是输入输出参数。
在函数内部,输入输出参数可以像普通变量一样使用,但是在调用函数时,必须传递变量的引用,而不是常规的变量值。这样,函数内部对输入输出参数的修改就会反映到调用者的变量上。
需要注意的是,输入输出参数不能有默认值,并且不能是可变参数(variadic parameters)的一部分。
在Swift中,除了常规的函数参数,还可以使用输入输出参数(in-out parameters)。输入输出参数允许函数改变函数体外部变量的值,并将这些更改传递回调用者。使用输入输出参数,你可以在调用函数时直接修改外部变量的值,而不需要通过函数的返回值来实现。
下面是一个使用输入输出参数的简单示例:
```swift
func swapInts(_ a: inout Int, _ b: inout Int) {
let temp = a
a = b
b = temp
}
var x = 5
var y = 10
print("Before swapping: x = \(x), y = \(y)")
swapInts(&x, &y)
print("After swapping: x = \(x), y = \(y)")
```
在这个示例中,我们定义了一个名为 `swapInts` 的函数,它接受两个输入输出参数 `a` 和 `b`,这两个参数被标记为 `inout`。函数内部交换了 `a` 和 `b` 的值。在调用 `swapInts` 函数时,我们必须在参数前面加上 `&` 符号,以指示这是输入输出参数。
在函数内部,输入输出参数可以像普通变量一样使用,但是在调用函数时,必须传递变量的引用,而不是常规的变量值。这样,函数内部对输入输出参数的修改就会反映到调用者的变量上。
需要注意的是,输入输出参数不能有默认值,并且不能是可变参数(variadic parameters)的一部分。
使用的场景,传入参数为使用的&是什么意思
## Inout 参数在 Swift 中的主要用途
Inout 参数在 Swift 中主要用于以下两种场景:
1. **修改函数参数的值:** 当您需要在函数内部修改传递给函数的变量的值时,可以使用 inout 参数。例如,以下函数使用 inout 参数交换两个变量的值:
```swift
func swapValues(a: inout Int, b: inout Int) {
let temp = a
a = b
b = temp
}
var x = 5
var y = 10
swapValues(a: &x, b: &y)
print(x) // 输出:10
print(y) // 输出:5
```
2. **更新共享资源:** 当您需要更新多个函数共享的资源时,可以使用 inout 参数。例如,以下函数使用 inout 参数更新一个全局计数器的值:
```swift
var globalCounter = 0
func incrementCounter(counter: inout Int) {
counter += 1
}
incrementCounter(counter: &globalCounter)
print(globalCounter) // 输出:1
```
## Inout 参数中的 & 符号
在 inout 参数中,`&` 符号用于指示传递给函数的参数的地址。这意味着函数可以直接访问和修改原始变量的值,而无需创建副本。
例如,在前面的 `swapValues` 函数中,`&x` 和 `&y` 表示传递给函数的 `x` 和 `y` 变量的地址。函数内部的 `a` 和 `b` 参数只是对原始 `x` 和 `y` 变量的别名。因此,当函数修改 `a` 和 `b` 的值时,它实际上是在修改原始 `x` 和 `y` 变量的值。
## 使用 inout 参数的注意事项
使用 inout 参数时需要注意以下几点:
* Inout 参数只能用于函数参数,不能用于返回值。
* Inout 参数不能与默认值一起使用。
* Inout 参数不能与 varargs 或可变参数一起使用。
* Inout 参数不能与函数类型一起使用。
## 总结
Inout 参数是 Swift 中一种强大的工具,可用于修改函数参数的值和更新共享资源。但是,需要注意的是,inout 参数有一些限制,并且在使用时需要谨慎。
如果您还有其他问题,请告诉我。
Inout 参数在 Swift 中主要用于以下两种场景:
1. **修改函数参数的值:** 当您需要在函数内部修改传递给函数的变量的值时,可以使用 inout 参数。例如,以下函数使用 inout 参数交换两个变量的值:
```swift
func swapValues(a: inout Int, b: inout Int) {
let temp = a
a = b
b = temp
}
var x = 5
var y = 10
swapValues(a: &x, b: &y)
print(x) // 输出:10
print(y) // 输出:5
```
2. **更新共享资源:** 当您需要更新多个函数共享的资源时,可以使用 inout 参数。例如,以下函数使用 inout 参数更新一个全局计数器的值:
```swift
var globalCounter = 0
func incrementCounter(counter: inout Int) {
counter += 1
}
incrementCounter(counter: &globalCounter)
print(globalCounter) // 输出:1
```
## Inout 参数中的 & 符号
在 inout 参数中,`&` 符号用于指示传递给函数的参数的地址。这意味着函数可以直接访问和修改原始变量的值,而无需创建副本。
例如,在前面的 `swapValues` 函数中,`&x` 和 `&y` 表示传递给函数的 `x` 和 `y` 变量的地址。函数内部的 `a` 和 `b` 参数只是对原始 `x` 和 `y` 变量的别名。因此,当函数修改 `a` 和 `b` 的值时,它实际上是在修改原始 `x` 和 `y` 变量的值。
## 使用 inout 参数的注意事项
使用 inout 参数时需要注意以下几点:
* Inout 参数只能用于函数参数,不能用于返回值。
* Inout 参数不能与默认值一起使用。
* Inout 参数不能与 varargs 或可变参数一起使用。
* Inout 参数不能与函数类型一起使用。
## 总结
Inout 参数是 Swift 中一种强大的工具,可用于修改函数参数的值和更新共享资源。但是,需要注意的是,inout 参数有一些限制,并且在使用时需要谨慎。
如果您还有其他问题,请告诉我。
注意
可变参数不能标记为inout
inout参数不能有默认值
inout参数只能传入可以被多次赋值的
inout参数的本质是地址传递(引用传递)
函数的重载
规则
函数名称相同基础上满足一下条件均可重载
参数个数不同
参数类型不同
参数标签不同
注意
返回值类型与函数重载无关
默认参数值和函数重载一起使用产生二义性时,编译器并不会报错(在c++中会报错),这种情况要注意了。
可变参数、省略参数标签、函数重载一起使用产生二义性时,编译器有可能会报错
介绍
在Swift中,函数重载(Function Overloading)是指在同一个作用域内定义多个同名函数,但它们的参数列表或返回类型不同。这允许你使用相同的函数名来执行不同的操作,而不必为每个操作使用不同的函数名。
下面是一个函数重载的简单示例:
```swift
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
func add(_ a: Double, _ b: Double) -> Double {
return a + b
}
```
在这个示例中,我们定义了两个名为 `add` 的函数,它们的参数列表分别接受两个整数和两个双精度浮点数。第一个 `add` 函数返回两个整数的和,而第二个 `add` 函数返回两个双精度浮点数的和。这两个函数具有相同的函数名,但参数列表和/或返回类型不同。
你可以像下面这样调用这两个函数:
```swift
let sum1 = add(5, 3)
print(sum1) // 输出: 8
let sum2 = add(2.5, 3.7)
print(sum2) // 输出: 6.2
```
在调用 `add` 函数时,编译器会根据传递给函数的参数类型自动确定要调用的函数版本。这样,你可以使用相同的函数名来处理不同类型的数据,提高了代码的可读性和灵活性。
下面是一个函数重载的简单示例:
```swift
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
func add(_ a: Double, _ b: Double) -> Double {
return a + b
}
```
在这个示例中,我们定义了两个名为 `add` 的函数,它们的参数列表分别接受两个整数和两个双精度浮点数。第一个 `add` 函数返回两个整数的和,而第二个 `add` 函数返回两个双精度浮点数的和。这两个函数具有相同的函数名,但参数列表和/或返回类型不同。
你可以像下面这样调用这两个函数:
```swift
let sum1 = add(5, 3)
print(sum1) // 输出: 8
let sum2 = add(2.5, 3.7)
print(sum2) // 输出: 6.2
```
在调用 `add` 函数时,编译器会根据传递给函数的参数类型自动确定要调用的函数版本。这样,你可以使用相同的函数名来处理不同类型的数据,提高了代码的可读性和灵活性。
内置函数
控制台输出函数print的使用
Swift提供了许多内置函数,这些函数不需要显式导入,可以直接在你的代码中使用。下面是一些常用的内置函数:
1. `print(_:separator:terminator:)`:打印信息到控制台。
```swift
print("Hello, world!")
```
2. `max(_:_:)` 和 `min(_:_:)`:返回两个值中的最大值和最小值。
```swift
let maxNumber = max(10, 20)
let minNumber = min(10, 20)
```
3. `abs(_:)`:返回一个数的绝对值。
```swift
let absoluteValue = abs(-5)
```
4. `sqrt(_:)`:返回一个数的平方根。
```swift
let squareRoot = sqrt(25)
```
5. `round(_:)` 和 `ceil(_:)` 和 `floor(_:)`:分别返回一个数的四舍五入值、向上取整值和向下取整值。
```swift
let roundedValue = round(3.7)
let ceiledValue = ceil(3.2)
let flooredValue = floor(3.9)
```
6. `sort(_:)`:对数组进行排序。
```swift
var numbers = [5, 3, 9, 1, 7]
numbers.sort()
```
7. `map(_:)` 和 `filter(_:)` 和 `reduce(_:_:)`:分别对数组中的每个元素执行操作、过滤数组中的元素以及将数组中的元素合并成单个值。
```swift
let doubledNumbers = numbers.map { $0 * 2 }
let filteredNumbers = numbers.filter { $0 > 5 }
let sum = numbers.reduce(0, +)
```
这些是一些常用的内置函数,Swift还提供了许多其他功能强大的内置函数,可以根据需要进行查阅使用。
Swift提供了许多内置函数,这些函数不需要显式导入,可以直接在你的代码中使用。下面是一些常用的内置函数:
1. `print(_:separator:terminator:)`:打印信息到控制台。
```swift
print("Hello, world!")
```
2. `max(_:_:)` 和 `min(_:_:)`:返回两个值中的最大值和最小值。
```swift
let maxNumber = max(10, 20)
let minNumber = min(10, 20)
```
3. `abs(_:)`:返回一个数的绝对值。
```swift
let absoluteValue = abs(-5)
```
4. `sqrt(_:)`:返回一个数的平方根。
```swift
let squareRoot = sqrt(25)
```
5. `round(_:)` 和 `ceil(_:)` 和 `floor(_:)`:分别返回一个数的四舍五入值、向上取整值和向下取整值。
```swift
let roundedValue = round(3.7)
let ceiledValue = ceil(3.2)
let flooredValue = floor(3.9)
```
6. `sort(_:)`:对数组进行排序。
```swift
var numbers = [5, 3, 9, 1, 7]
numbers.sort()
```
7. `map(_:)` 和 `filter(_:)` 和 `reduce(_:_:)`:分别对数组中的每个元素执行操作、过滤数组中的元素以及将数组中的元素合并成单个值。
```swift
let doubledNumbers = numbers.map { $0 * 2 }
let filteredNumbers = numbers.filter { $0 > 5 }
let sum = numbers.reduce(0, +)
```
这些是一些常用的内置函数,Swift还提供了许多其他功能强大的内置函数,可以根据需要进行查阅使用。
内联函数
@inline
@inline(never) func test () -> Void {}
//永远不会被内联(即使开启了编译器优化)
@inline(__always) func test () {}
// 开启编译器优化后,即使代码很长,也会被内联(递归函数、动态派发的函数除外)
@inline func test() {}
开启编译器优化后,系统自动进行内联优化,一般一下情况不进行内联优化
函数体比较长
包含递归调用
包含动态派发
是什么
在 Swift 中,内联函数(inline functions)是指在编译时将函数调用直接替换为函数体的一种优化方式。内联函数可以提高代码的执行效率,因为它减少了函数调用的开销,特别是对于频繁调用的小型函数而言。
Swift 并没有提供显式的关键字来声明函数为内联函数,而是由编译器自动决定是否将函数进行内联。编译器会基于函数的大小、上下文和其他因素来决定是否内联函数。
通常情况下,Swift 会自动将简短的函数内联。例如,一个简单的函数,它的函数体很短,可能被频繁调用,这种情况下编译器会倾向于将其内联。
另外,在某些情况下,你可以使用 `@inline(__always)` 属性来强制要求编译器将函数内联。这样做通常是出于优化的目的,但需要注意过度使用内联可能会导致代码体积增加,因此应谨慎使用。
```swift
@inline(__always)
func inlineFunction() {
// 函数体
}
```
需要注意的是,虽然内联函数可以提高执行效率,但在某些情况下它可能会增加代码大小。因此,在使用 `@inline(__always)` 强制内联函数时,需要仔细权衡,确保它带来的性能提升能够超过因代码增大而带来的额外开销。
Swift 并没有提供显式的关键字来声明函数为内联函数,而是由编译器自动决定是否将函数进行内联。编译器会基于函数的大小、上下文和其他因素来决定是否内联函数。
通常情况下,Swift 会自动将简短的函数内联。例如,一个简单的函数,它的函数体很短,可能被频繁调用,这种情况下编译器会倾向于将其内联。
另外,在某些情况下,你可以使用 `@inline(__always)` 属性来强制要求编译器将函数内联。这样做通常是出于优化的目的,但需要注意过度使用内联可能会导致代码体积增加,因此应谨慎使用。
```swift
@inline(__always)
func inlineFunction() {
// 函数体
}
```
需要注意的是,虽然内联函数可以提高执行效率,但在某些情况下它可能会增加代码大小。因此,在使用 `@inline(__always)` 强制内联函数时,需要仔细权衡,确保它带来的性能提升能够超过因代码增大而带来的额外开销。
函数类型
是什么
在Swift中,函数也是一种类型,你可以将函数作为变量、常量或参数传递给其他函数。函数类型描述了函数的参数类型和返回类型。函数类型可以写成 `(参数类型) -> 返回类型` 的形式。
以下是一些示例:
1. **没有参数,没有返回值的函数类型**:
```swift
() -> Void
```
这表示一个没有参数,也没有返回值的函数类型。
2. **接受一个整数参数,返回一个整数的函数类型**:
```swift
(Int) -> Int
```
这表示一个接受一个整数参数,返回一个整数的函数类型。
3. **接受两个字符串参数,没有返回值的函数类型**:
```swift
(String, String) -> Void
```
这表示一个接受两个字符串参数,没有返回值的函数类型。
4. **接受一个整数和一个浮点数参数,返回一个布尔值的函数类型**:
```swift
(Int, Double) -> Bool
```
这表示一个接受一个整数和一个浮点数参数,返回一个布尔值的函数类型。
你可以声明变量、常量或函数参数的类型为函数类型,并将相应的函数赋值给它们,例如:
```swift
var mathFunction: (Int, Int) -> Int
mathFunction = addTwoNumbers
```
在这个例子中,我们声明了一个变量 `mathFunction` 的类型为接受两个整数参数并返回一个整数的函数类型。然后,我们将 `addTwoNumbers` 函数赋值给了这个变量。
函数类型使得 Swift 中的函数可以像其他数据类型一样进行操作和传递,这为函数式编程风格提供了强大的支持。
以下是一些示例:
1. **没有参数,没有返回值的函数类型**:
```swift
() -> Void
```
这表示一个没有参数,也没有返回值的函数类型。
2. **接受一个整数参数,返回一个整数的函数类型**:
```swift
(Int) -> Int
```
这表示一个接受一个整数参数,返回一个整数的函数类型。
3. **接受两个字符串参数,没有返回值的函数类型**:
```swift
(String, String) -> Void
```
这表示一个接受两个字符串参数,没有返回值的函数类型。
4. **接受一个整数和一个浮点数参数,返回一个布尔值的函数类型**:
```swift
(Int, Double) -> Bool
```
这表示一个接受一个整数和一个浮点数参数,返回一个布尔值的函数类型。
你可以声明变量、常量或函数参数的类型为函数类型,并将相应的函数赋值给它们,例如:
```swift
var mathFunction: (Int, Int) -> Int
mathFunction = addTwoNumbers
```
在这个例子中,我们声明了一个变量 `mathFunction` 的类型为接受两个整数参数并返回一个整数的函数类型。然后,我们将 `addTwoNumbers` 函数赋值给了这个变量。
函数类型使得 Swift 中的函数可以像其他数据类型一样进行操作和传递,这为函数式编程风格提供了强大的支持。
由两部分组成
形式参数类型
返回值类型
函数类型作为函数参数例子
在Swift中,你可以将函数类型作为函数的参数传递给另一个函数,这种功能使得函数式编程风格非常强大和灵活。以下是一个示例:
```swift
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
func subtract(_ a: Int, _ b: Int) -> Int {
return a - b
}
func mathOperation(_ operation: (Int, Int) -> Int, _ a: Int, _ b: Int) -> Int {
return operation(a, b)
}
let result1 = mathOperation(add, 10, 5)
print(result1) // 输出: 15
let result2 = mathOperation(subtract, 10, 5)
print(result2) // 输出: 5
```
在这个示例中,我们定义了两个简单的数学运算函数 `add` 和 `subtract`,它们分别接受两个整数参数并返回一个整数。然后,我们定义了一个名为 `mathOperation` 的函数,它接受一个函数类型的参数 `operation`,以及两个整数参数 `a` 和 `b`。`mathOperation` 函数调用传递进来的 `operation` 函数,并将 `a` 和 `b` 作为参数传递给它,然后返回结果。
在调用 `mathOperation` 函数时,我们可以将 `add` 和 `subtract` 函数作为参数传递进去,因为它们的类型符合 `mathOperation` 函数所期望的 `(Int, Int) -> Int` 类型。
通过将函数类型作为参数传递,我们可以实现更加灵活和抽象的函数组合,这是函数式编程风格的一大特点。
```swift
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
func subtract(_ a: Int, _ b: Int) -> Int {
return a - b
}
func mathOperation(_ operation: (Int, Int) -> Int, _ a: Int, _ b: Int) -> Int {
return operation(a, b)
}
let result1 = mathOperation(add, 10, 5)
print(result1) // 输出: 15
let result2 = mathOperation(subtract, 10, 5)
print(result2) // 输出: 5
```
在这个示例中,我们定义了两个简单的数学运算函数 `add` 和 `subtract`,它们分别接受两个整数参数并返回一个整数。然后,我们定义了一个名为 `mathOperation` 的函数,它接受一个函数类型的参数 `operation`,以及两个整数参数 `a` 和 `b`。`mathOperation` 函数调用传递进来的 `operation` 函数,并将 `a` 和 `b` 作为参数传递给它,然后返回结果。
在调用 `mathOperation` 函数时,我们可以将 `add` 和 `subtract` 函数作为参数传递进去,因为它们的类型符合 `mathOperation` 函数所期望的 `(Int, Int) -> Int` 类型。
通过将函数类型作为参数传递,我们可以实现更加灵活和抽象的函数组合,这是函数式编程风格的一大特点。
类型别名(typealias)
在 Swift 中,你可以使用类型别名(Type Aliases)为已存在的类型定义一个新的名字。类型别名可以为复杂的类型提供更简短的名字,增加代码的可读性和可维护性。类型别名使用 `typealias` 关键字来定义。
以下是一个简单的示例,将 `Int` 类型的别名定义为 `MyInteger`:
```swift
typealias MyInteger = Int
```
现在,`MyInteger` 就是 `Int` 的别名,你可以使用 `MyInteger` 来代替 `Int`:
```swift
let number: MyInteger = 10
```
类型别名也可以用于复杂类型,比如函数类型:
```swift
typealias MathOperation = (Int, Int) -> Int
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
let operation: MathOperation = add
let result = operation(5, 3) // 结果为 8
```
在这个示例中,我们将 `(Int, Int) -> Int` 类型的函数定义为 `MathOperation` 类型别名,然后使用 `MathOperation` 来声明一个变量 `operation`,并将 `add` 函数赋值给它。这样我们就可以使用 `operation` 来调用 `add` 函数了。
类型别名使得代码更具可读性和可维护性,尤其是在处理复杂类型时,使用别名可以使代码更清晰易懂。
在 Swift 中,你可以使用类型别名(Type Aliases)为已存在的类型定义一个新的名字。类型别名可以为复杂的类型提供更简短的名字,增加代码的可读性和可维护性。类型别名使用 `typealias` 关键字来定义。
以下是一个简单的示例,将 `Int` 类型的别名定义为 `MyInteger`:
```swift
typealias MyInteger = Int
```
现在,`MyInteger` 就是 `Int` 的别名,你可以使用 `MyInteger` 来代替 `Int`:
```swift
let number: MyInteger = 10
```
类型别名也可以用于复杂类型,比如函数类型:
```swift
typealias MathOperation = (Int, Int) -> Int
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
let operation: MathOperation = add
let result = operation(5, 3) // 结果为 8
```
在这个示例中,我们将 `(Int, Int) -> Int` 类型的函数定义为 `MathOperation` 类型别名,然后使用 `MathOperation` 来声明一个变量 `operation`,并将 `add` 函数赋值给它。这样我们就可以使用 `operation` 来调用 `add` 函数了。
类型别名使得代码更具可读性和可维护性,尤其是在处理复杂类型时,使用别名可以使代码更清晰易懂。
Void本质
Void本质是空元组
public typealias Void = ()
源码中的定义
在 Swift 中,`Void` 是一种空元组类型,表示一个不包含任何值的元组。因此,`Void` 类型实际上等同于空元组 `()`。
你可以将 `Void` 看作是一个空的容器,它不包含任何数据。在函数声明中,如果函数不返回任何值,则通常会使用 `Void` 类型作为返回类型,以表示该函数不返回任何值。
举个例子,在函数中如果没有明确指定返回值,你可以使用 `Void` 类型来表示这个函数不返回任何内容:
```swift
func printHello() -> Void {
print("Hello")
}
func doSomething() -> () {
// 这里是函数体,没有返回任何内容
}
```
或者,你可以直接省略返回类型,Swift 会自动推断为 `Void`:
```swift
func printHello() {
print("Hello")
}
```
在其他上下文中,你也可以使用空元组 `()` 来表示 `Void` 类型。例如,作为元组的类型标注,或者作为泛型类型参数的占位符。
总的来说,`Void` 类型是一种在 Swift 中用来表示空值或没有值的方式,通常用于表示不返回任何内容的函数或泛型类型。
你可以将 `Void` 看作是一个空的容器,它不包含任何数据。在函数声明中,如果函数不返回任何值,则通常会使用 `Void` 类型作为返回类型,以表示该函数不返回任何值。
举个例子,在函数中如果没有明确指定返回值,你可以使用 `Void` 类型来表示这个函数不返回任何内容:
```swift
func printHello() -> Void {
print("Hello")
}
func doSomething() -> () {
// 这里是函数体,没有返回任何内容
}
```
或者,你可以直接省略返回类型,Swift 会自动推断为 `Void`:
```swift
func printHello() {
print("Hello")
}
```
在其他上下文中,你也可以使用空元组 `()` 来表示 `Void` 类型。例如,作为元组的类型标注,或者作为泛型类型参数的占位符。
总的来说,`Void` 类型是一种在 Swift 中用来表示空值或没有值的方式,通常用于表示不返回任何内容的函数或泛型类型。
嵌套函数
将函数定义在函数内部
可以用来做什么
可以根据入参,调用内部定义的不同的函数。
在 Swift 中,你可以在一个函数内部定义另一个函数,这种函数称为嵌套函数(Nested Functions)。嵌套函数可以访问外部函数的变量,甚至可以访问外部函数的参数。嵌套函数在外部函数的作用域内有效,但它们不能被外部函数之外的代码调用。
以下是一个简单的示例:
```swift
func outerFunction() -> Int {
var x = 10
// 嵌套函数
func innerFunction(y: Int) -> Int {
return x + y
}
return innerFunction(y: 5)
}
let result = outerFunction()
print(result) // 输出: 15
```
在这个例子中,`innerFunction` 是 `outerFunction` 的嵌套函数。它可以访问 `outerFunction` 中的变量 `x`,并且可以使用外部函数的参数 `y`。在 `outerFunction` 中调用 `innerFunction` 并返回结果。
嵌套函数的一个常见用途是在一个函数中定义一个辅助函数,这样可以使代码更加模块化和易于理解。此外,由于嵌套函数只在外部函数中可见,这样可以避免污染全局命名空间。
以下是一个简单的示例:
```swift
func outerFunction() -> Int {
var x = 10
// 嵌套函数
func innerFunction(y: Int) -> Int {
return x + y
}
return innerFunction(y: 5)
}
let result = outerFunction()
print(result) // 输出: 15
```
在这个例子中,`innerFunction` 是 `outerFunction` 的嵌套函数。它可以访问 `outerFunction` 中的变量 `x`,并且可以使用外部函数的参数 `y`。在 `outerFunction` 中调用 `innerFunction` 并返回结果。
嵌套函数的一个常见用途是在一个函数中定义一个辅助函数,这样可以使代码更加模块化和易于理解。此外,由于嵌套函数只在外部函数中可见,这样可以避免污染全局命名空间。
总结:封装思想、迪米特法则的体现
04、枚举
枚举的基本用法
在 Swift 中,枚举(Enumeration)是一种用于定义一组相关值的通用数据类型。枚举类型允许你以清晰明了的方式来表示一组有限的可能性。枚举可以用于在代码中定义一组特定的值,并且在需要时可以轻松地引用这些值。枚举在Swift中是非常强大且常用的特性之一,你可以使用它来定义特定的状态、选项、错误类型等。
Swift 中的枚举与其他编程语言中的枚举有些相似,但也有一些特殊的特性,例如:
1. **支持关联值**:Swift 的枚举可以关联额外的值,使得枚举成员能够携带更多的信息。这使得枚举更加灵活,可以应对更多的场景。
2. **支持原始值**:Swift 的枚举可以关联原始值,每个枚举成员都有一个预先定义的原始值。原始值可以是整数、浮点数、字符串或者其他一些类型。
3. **支持方法和计算属性**:你可以在 Swift 的枚举中定义方法和计算属性,使得枚举更加强大。
4. **支持协议**:Swift 的枚举可以遵循协议,从而使其更加灵活,可以适应不同的需求。
5. **支持迭代**:通过遵循`CaseIterable`协议,你可以让枚举变得可迭代,从而可以轻松地遍历枚举中的所有成员。
在 Swift 中,枚举可以用于多种情况,例如表示状态、选项、错误类型等。它提供了一种清晰、安全且灵活的方式来组织代码和数据。
以下是一个简单的枚举示例以及如何使用它:
```swift
// 定义一个名为CompassPoint的枚举
enum CompassPoint {
case north
case south
case east
case west
}
// 使用枚举
var directionToGo = CompassPoint.west
directionToGo = .east
// 使用switch语句匹配枚举值
switch directionToGo {
case .north:
print("Going North")
case .south:
print("Going South")
case .east:
print("Going East")
case .west:
print("Going West")
}
```
在这个例子中,我们定义了一个名为`CompassPoint`的枚举,它包含了四个可能的方向:北、南、东、西。然后,我们创建了一个变量`directionToGo`,并将其设置为枚举成员`CompassPoint.west`。接着,我们使用点语法将变量设置为`CompassPoint.east`。最后,我们使用`switch`语句来匹配枚举值,并打印出相应的信息。
除了简单的枚举之外,Swift 还支持枚举关联值和原始值,使得枚举更加灵活和强大。例如,你可以在枚举成员中存储额外的相关数据,或者为枚举成员指定预定义的原始值。
在实际开发中,你可以使用枚举来表示应用程序中的各种状态、选项或者其他有限的可能性。Swift 的枚举是一种强大的工具,可以帮助你编写更清晰、更安全的代码。
Swift 中的枚举与其他编程语言中的枚举有些相似,但也有一些特殊的特性,例如:
1. **支持关联值**:Swift 的枚举可以关联额外的值,使得枚举成员能够携带更多的信息。这使得枚举更加灵活,可以应对更多的场景。
2. **支持原始值**:Swift 的枚举可以关联原始值,每个枚举成员都有一个预先定义的原始值。原始值可以是整数、浮点数、字符串或者其他一些类型。
3. **支持方法和计算属性**:你可以在 Swift 的枚举中定义方法和计算属性,使得枚举更加强大。
4. **支持协议**:Swift 的枚举可以遵循协议,从而使其更加灵活,可以适应不同的需求。
5. **支持迭代**:通过遵循`CaseIterable`协议,你可以让枚举变得可迭代,从而可以轻松地遍历枚举中的所有成员。
在 Swift 中,枚举可以用于多种情况,例如表示状态、选项、错误类型等。它提供了一种清晰、安全且灵活的方式来组织代码和数据。
以下是一个简单的枚举示例以及如何使用它:
```swift
// 定义一个名为CompassPoint的枚举
enum CompassPoint {
case north
case south
case east
case west
}
// 使用枚举
var directionToGo = CompassPoint.west
directionToGo = .east
// 使用switch语句匹配枚举值
switch directionToGo {
case .north:
print("Going North")
case .south:
print("Going South")
case .east:
print("Going East")
case .west:
print("Going West")
}
```
在这个例子中,我们定义了一个名为`CompassPoint`的枚举,它包含了四个可能的方向:北、南、东、西。然后,我们创建了一个变量`directionToGo`,并将其设置为枚举成员`CompassPoint.west`。接着,我们使用点语法将变量设置为`CompassPoint.east`。最后,我们使用`switch`语句来匹配枚举值,并打印出相应的信息。
除了简单的枚举之外,Swift 还支持枚举关联值和原始值,使得枚举更加灵活和强大。例如,你可以在枚举成员中存储额外的相关数据,或者为枚举成员指定预定义的原始值。
在实际开发中,你可以使用枚举来表示应用程序中的各种状态、选项或者其他有限的可能性。Swift 的枚举是一种强大的工具,可以帮助你编写更清晰、更安全的代码。
关联值
概念以及使用方法
这个厉害了,使枚举具备了更加强大的功能。可以用来适配多种数据格式,使枚举的功能更加强大,参考pdf中密码的例子,数字密码和手势密码。
枚举的关联值是一种特性,它允许枚举成员携带额外的相关数据。这样,枚举成员就可以在需要时存储和传递更多的信息。使用关联值,你可以在定义枚举时为每个成员指定一种或多种类型的数据,以便在程序中使用。
以下是一个示例,展示了如何在Swift中使用枚举的关联值:
```swift
// 定义一个名为Student的枚举,它有两个关联值,分别表示学生的姓名和分数
enum Student {
case undergraduate(name: String, score: Double)
case graduate(name: String, score: Double)
}
// 创建两个不同类型的学生
let undergrad = Student.undergraduate(name: "Alice", score: 85.0)
let grad = Student.graduate(name: "Bob", score: 92.5)
// 使用switch语句访问关联值
switch undergrad {
case .undergraduate(let name, let score):
print("Undergraduate student \(name) scored \(score)")
case .graduate(let name, let score):
print("Graduate student \(name) scored \(score)")
}
// 使用if case语句进行模式匹配和访问关联值
if case let .graduate(name, score) = grad {
print("Graduate student \(name) scored \(score)")
}
```
在这个示例中,我们定义了一个名为`Student`的枚举,它有两个成员:`undergraduate`和`graduate`,每个成员都有两个关联值,分别表示学生的姓名和分数。然后,我们创建了两个不同类型的学生,并使用`switch`语句和`if case`语句来访问关联值,并打印出相应的信息。
关联值使得枚举更加灵活,能够应对各种不同情况下的需求。你可以根据具体的场景为枚举成员指定不同类型的关联值,并在需要时方便地访问和处理这些值。
枚举的关联值是一种特性,它允许枚举成员携带额外的相关数据。这样,枚举成员就可以在需要时存储和传递更多的信息。使用关联值,你可以在定义枚举时为每个成员指定一种或多种类型的数据,以便在程序中使用。
以下是一个示例,展示了如何在Swift中使用枚举的关联值:
```swift
// 定义一个名为Student的枚举,它有两个关联值,分别表示学生的姓名和分数
enum Student {
case undergraduate(name: String, score: Double)
case graduate(name: String, score: Double)
}
// 创建两个不同类型的学生
let undergrad = Student.undergraduate(name: "Alice", score: 85.0)
let grad = Student.graduate(name: "Bob", score: 92.5)
// 使用switch语句访问关联值
switch undergrad {
case .undergraduate(let name, let score):
print("Undergraduate student \(name) scored \(score)")
case .graduate(let name, let score):
print("Graduate student \(name) scored \(score)")
}
// 使用if case语句进行模式匹配和访问关联值
if case let .graduate(name, score) = grad {
print("Graduate student \(name) scored \(score)")
}
```
在这个示例中,我们定义了一个名为`Student`的枚举,它有两个成员:`undergraduate`和`graduate`,每个成员都有两个关联值,分别表示学生的姓名和分数。然后,我们创建了两个不同类型的学生,并使用`switch`语句和`if case`语句来访问关联值,并打印出相应的信息。
关联值使得枚举更加灵活,能够应对各种不同情况下的需求。你可以根据具体的场景为枚举成员指定不同类型的关联值,并在需要时方便地访问和处理这些值。
原始值
是什么
在 Swift 中,枚举的原始值(Raw Values)是一种预先定义的值,每个枚举成员都具有一个相应的原始值。原始值必须是相同类型的常量,可以是整数、浮点数、字符串或者其他一些特定的类型。通过为枚举成员指定原始值,可以在枚举实例中直接访问这些值,而不需要对其进行额外的计算或处理。
原始值可以让你在定义枚举时预先为每个成员指定一个固定的值,这在一些情况下非常有用,例如:
1. 定义一组特定的选项或标识符,并将它们映射到特定的值。
2. 与外部系统进行交互时,使用预定义的值进行匹配。
以下是一个使用原始值的示例:
```swift
// 定义一个表示月份的枚举,使用整数作为原始值
enum Month: Int {
case january = 1, february, march, april, may, june, july, august, september, october, november, december
}
// 使用原始值创建枚举实例
let currentMonth = Month(rawValue: 4)
// 输出对应的月份名称
if let month = currentMonth {
print("The current month is \(month)")
} else {
print("Invalid month")
}
```
在这个示例中,我们定义了一个名为`Month`的枚举,它包含了一年中的所有月份,其中每个月份的原始值从 1 开始递增。然后,我们使用`Month(rawValue:)`方法根据原始值创建了一个枚举实例,并打印出对应的月份名称。
需要注意的是,使用原始值创建枚举实例时,返回的结果是一个可选类型(Optional)。这是因为枚举的原始值可能无效,所以需要使用可选绑定来安全地访问枚举实例。
总的来说,原始值为枚举成员提供了一个预定义的值,使得在程序中可以方便地使用这些值,而不需要进行额外的计算或处理。这在某些情况下可以简化代码,并提高代码的可读性和可维护性。
原始值可以让你在定义枚举时预先为每个成员指定一个固定的值,这在一些情况下非常有用,例如:
1. 定义一组特定的选项或标识符,并将它们映射到特定的值。
2. 与外部系统进行交互时,使用预定义的值进行匹配。
以下是一个使用原始值的示例:
```swift
// 定义一个表示月份的枚举,使用整数作为原始值
enum Month: Int {
case january = 1, february, march, april, may, june, july, august, september, october, november, december
}
// 使用原始值创建枚举实例
let currentMonth = Month(rawValue: 4)
// 输出对应的月份名称
if let month = currentMonth {
print("The current month is \(month)")
} else {
print("Invalid month")
}
```
在这个示例中,我们定义了一个名为`Month`的枚举,它包含了一年中的所有月份,其中每个月份的原始值从 1 开始递增。然后,我们使用`Month(rawValue:)`方法根据原始值创建了一个枚举实例,并打印出对应的月份名称。
需要注意的是,使用原始值创建枚举实例时,返回的结果是一个可选类型(Optional)。这是因为枚举的原始值可能无效,所以需要使用可选绑定来安全地访问枚举实例。
总的来说,原始值为枚举成员提供了一个预定义的值,使得在程序中可以方便地使用这些值,而不需要进行额外的计算或处理。这在某些情况下可以简化代码,并提高代码的可读性和可维护性。
枚举成员可以使用相同类型的默认值预先对应,这个默认值叫做:原始值
原始值不占用枚举变量的内存
隐式原始值
如果枚举的原始值类型是Int String,swift会自动分类原始值。
Int
0...n
String
"枚举成员的变量名的字符串"
递归枚举
分类
indirect(间接的)
arith(算术)
在 Swift 中,递归枚举是一种定义枚举类型的方式,其中枚举类型的一个或多个 case 关联到该枚举类型本身。这种技术在处理树形数据结构等问题时非常有用。让我们来看一个例子:
```swift
// 定义一个递归枚举来表示算术表达式
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
// 创建一个算术表达式 ((2 + 3) * 5)
let expression = ArithmeticExpression.multiplication(
ArithmeticExpression.addition(
.number(2),
.number(3)
),
.number(5)
)
// 定义一个计算函数来评估这个表达式
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case let .number(value):
return value
case let .addition(left, right):
return evaluate(left) + evaluate(right)
case let .multiplication(left, right):
return evaluate(left) * evaluate(right)
}
}
// 评估并打印结果
print(evaluate(expression)) // 输出: 25
```
在这个例子中,我们定义了一个表示算术表达式的递归枚举 `ArithmeticExpression`。该枚举有三种情况:一个数字,一个加法表达式和一个乘法表达式。`indirect` 关键字用于告诉 Swift 允许该枚举类型的成员之间相互引用,从而使得枚举成员可以是递归的。然后我们创建了一个复杂的表达式,并通过 `evaluate` 函数计算其值。
```swift
// 定义一个递归枚举来表示算术表达式
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
// 创建一个算术表达式 ((2 + 3) * 5)
let expression = ArithmeticExpression.multiplication(
ArithmeticExpression.addition(
.number(2),
.number(3)
),
.number(5)
)
// 定义一个计算函数来评估这个表达式
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case let .number(value):
return value
case let .addition(left, right):
return evaluate(left) + evaluate(right)
case let .multiplication(left, right):
return evaluate(left) * evaluate(right)
}
}
// 评估并打印结果
print(evaluate(expression)) // 输出: 25
```
在这个例子中,我们定义了一个表示算术表达式的递归枚举 `ArithmeticExpression`。该枚举有三种情况:一个数字,一个加法表达式和一个乘法表达式。`indirect` 关键字用于告诉 Swift 允许该枚举类型的成员之间相互引用,从而使得枚举成员可以是递归的。然后我们创建了一个复杂的表达式,并通过 `evaluate` 函数计算其值。
获取枚举占用的内存大小
MemoryLayout
举例
拔高
思考枚举的内存布局
进一步观察下枚举的内存布局
switch语句的底层是如何实现的?
05、可选类型
可选类型
类型根据是否可选,即是否可以赋空,类型又被分为可选类型和非可选类型两种
可选类型
语法
在类型名称后面加一个问号?来定义可选类型。
列子
var name: String? = "Jack" // name = nil is valid
声明的变量,允许被设置为nil
在 Swift 中,可选项(Optionals)是一种特殊的类型,用于处理可能缺失值的情况。在某些情况下,变量或常量可能没有值,此时可以使用可选项来表示这种可能性。
可选项的声明使用 `?` 符号,例如:
```swift
var optionalInt: Int?
```
这表示 `optionalInt` 是一个可选的整数。它可以存储一个整数值,也可以不存储任何值(即 `nil`)。
你可以使用 `if let` 或 `guard let` 语句来解包可选项,以安全地访问其中的值:
```swift
var optionalInt: Int? = 10
if let unwrappedInt = optionalInt {
print("The value of optionalInt is \(unwrappedInt)")
} else {
print("optionalInt is nil")
}
```
或者使用可选项的强制解包(Force Unwrapping):
```swift
var optionalInt: Int? = 10
let unwrappedInt = optionalInt!
print("The value of optionalInt is \(unwrappedInt)")
```
需要注意的是,强制解包在可选项为 `nil` 的情况下会导致运行时错误,因此应该谨慎使用。最好使用 `if let` 或 `guard let` 来安全地解包可选项。
此外,Swift 还提供了可选项的隐式解包(Implicitly Unwrapped Optionals),它们在声明时使用 `!` 符号,表示可选项在使用时会自动解包。通常情况下,隐式解包可选项用于确保一个变量或常量在被使用之前已经被初始化,并且永远不会为 `nil`。
可选项的声明使用 `?` 符号,例如:
```swift
var optionalInt: Int?
```
这表示 `optionalInt` 是一个可选的整数。它可以存储一个整数值,也可以不存储任何值(即 `nil`)。
你可以使用 `if let` 或 `guard let` 语句来解包可选项,以安全地访问其中的值:
```swift
var optionalInt: Int? = 10
if let unwrappedInt = optionalInt {
print("The value of optionalInt is \(unwrappedInt)")
} else {
print("optionalInt is nil")
}
```
或者使用可选项的强制解包(Force Unwrapping):
```swift
var optionalInt: Int? = 10
let unwrappedInt = optionalInt!
print("The value of optionalInt is \(unwrappedInt)")
```
需要注意的是,强制解包在可选项为 `nil` 的情况下会导致运行时错误,因此应该谨慎使用。最好使用 `if let` 或 `guard let` 来安全地解包可选项。
此外,Swift 还提供了可选项的隐式解包(Implicitly Unwrapped Optionals),它们在声明时使用 `!` 符号,表示可选项在使用时会自动解包。通常情况下,隐式解包可选项用于确保一个变量或常量在被使用之前已经被初始化,并且永远不会为 `nil`。
可选性是对其他类型的一层包装,可以将它理解为一个盒子
如果为nil,它是个空盒子
如果不为nil,那么盒子里装的是:被包装类型的数据。
非可选类型
let i:Int
i = 1 // i 不能被赋空,编辑器会报错
i = 1 // i 不能被赋空,编辑器会报错
解包
强制解包
如果要从可选选项中取出被包装的数据(将盒子里装的东西取出来),需要使用感叹号!进行强制解包
如果对值为nil的可选项进行强制解包,将会产生运行时错误Fatal error:unexpectedly found nil while unwarpping an Optional value
如果对值为nil的可选项进行强制解包,将会产生运行时错误Fatal error:unexpectedly found nil while unwarpping an Optional value
判断可选类型是否包含值
if obj != nil
while循环中使用可选项绑定
为了干什么
可选项到
安全解包:可选项绑定解包
可选项绑定(optional binding)
方法
if let
语法
单条件写法
if let unwrapped = optinal {}
`if let` 是 Swift 中用于安全解包可选值的一种常见模式,用于检查可选值是否包含非 nil 的值,并且在包含值的情况下将其解包并赋值给一个新的常量或变量。
```swift
var optionalString: String? = "Hello"
if let unwrappedString = optionalString {
// optionalString 包含值,且被解包赋值给 unwrappedString
print("The string is: \(unwrappedString)")
} else {
// optionalString 为 nil
print("The string is nil")
}
```
在上面的例子中,`if let` 语句用于检查 `optionalString` 是否包含一个非 nil 的值。如果 `optionalString` 包含值,则该值将被解包并赋值给 `unwrappedString`,然后执行 `if` 语句中的代码块。如果 `optionalString` 是 nil,则 `else` 分支中的代码块将被执行。
`if let` 的优势在于它提供了一种清晰且安全的方式来处理可选值,避免了强制解包可能导致的运行时错误。因此,它通常用于可选值的处理,特别是在需要使用可选值的情况下。
```swift
var optionalString: String? = "Hello"
if let unwrappedString = optionalString {
// optionalString 包含值,且被解包赋值给 unwrappedString
print("The string is: \(unwrappedString)")
} else {
// optionalString 为 nil
print("The string is nil")
}
```
在上面的例子中,`if let` 语句用于检查 `optionalString` 是否包含一个非 nil 的值。如果 `optionalString` 包含值,则该值将被解包并赋值给 `unwrappedString`,然后执行 `if` 语句中的代码块。如果 `optionalString` 是 nil,则 `else` 分支中的代码块将被执行。
`if let` 的优势在于它提供了一种清晰且安全的方式来处理可选值,避免了强制解包可能导致的运行时错误。因此,它通常用于可选值的处理,特别是在需要使用可选值的情况下。
多条件合并层写法:逗号隔开
if let first = Int("4"), let second = Int("42"), let first < second && second < 100 {}
guard let
语法
单条件写法:guard let unwrapped = optinal else {}
`guard let` 是 `guard` 语句的一个特定用法,通常用于对可选值进行解包,并且在解包失败时提前退出函数、方法、循环或代码块。
```swift
func processString(optionalString: String?) {
guard let unwrappedString = optionalString else {
print("The string is nil")
return
}
// 在这里可以安全地使用 unwrappedString,它是一个非可选的 String 值
print("The string is: \(unwrappedString)")
}
```
在上面的例子中,`guard let` 语句用于解包 `optionalString`,并将其非可选值赋给 `unwrappedString`。如果 `optionalString` 是 nil,则 `else` 分支中的代码块将被执行,输出 "The string is nil",然后函数提前退出。如果 `optionalString` 不是 nil,则继续执行 `guard` 语句后面的代码,安全地使用 `unwrappedString`。
`guard let` 的优点在于它提供了一种简洁的方式来处理可选值,并且可以减少嵌套的代码结构。这使得代码更易读,同时确保了可选值的安全处理。
```swift
func processString(optionalString: String?) {
guard let unwrappedString = optionalString else {
print("The string is nil")
return
}
// 在这里可以安全地使用 unwrappedString,它是一个非可选的 String 值
print("The string is: \(unwrappedString)")
}
```
在上面的例子中,`guard let` 语句用于解包 `optionalString`,并将其非可选值赋给 `unwrappedString`。如果 `optionalString` 是 nil,则 `else` 分支中的代码块将被执行,输出 "The string is nil",然后函数提前退出。如果 `optionalString` 不是 nil,则继续执行 `guard` 语句后面的代码,安全地使用 `unwrappedString`。
`guard let` 的优点在于它提供了一种简洁的方式来处理可选值,并且可以减少嵌套的代码结构。这使得代码更易读,同时确保了可选值的安全处理。
guard(警卫)规则
语法
guard 条件 else {// do something // 退出当前作用域 // return break、continue、throw error }
含义
当guard语句的条件为false时,就会执行大括号里面的代码
当guard语句的条件为true时,就会跳过guard语句
场景
特别适合,满足条件再往下执行,否则提前退出的场景
巧用
当使用guard语句进行可选项绑定时,绑定的常量(let)、变量(var)也能在外层作用于中使用。
这点真特码的爽
多条件和并写法:逗号隔开
guard let first = Int("4"), let second = Int("42"), let first < second && second < 100 else {}
空合并运算符??
语法
a??b
规则
a是可选项
b是可选项或者不是可选项
b跟a的存储类型必须相同
含义
如果a不为nil,就返回a
如果a为nil,就返回b
如果b不是可选项,返回a时会自动解包
这个很重要,这个很重要
在 Swift 中,双问号 `??` 是一个空合并运算符。它用于提供一个默认值,当左边的可选值为 nil 时,就返回右边的值。
但是,如果右边的值不是可选类型,而左边的值是可选类型,那么在编译时会发生错误,因为它们的类型不匹配。
如果你想要使用空合并运算符,并且右边的值不是可选类型,你可以在左边的可选值上使用强制解包来进行操作,像这样:
```swift
var optionalValue: Int? = nil
let defaultValue = 10
let result = optionalValue ?? defaultValue
```
在这个例子中,如果 `optionalValue` 是 nil,`result` 将会是 `defaultValue`,否则它将会是 `optionalValue` 解包后的值。
但是,如果右边的值不是可选类型,而左边的值是可选类型,那么在编译时会发生错误,因为它们的类型不匹配。
如果你想要使用空合并运算符,并且右边的值不是可选类型,你可以在左边的可选值上使用强制解包来进行操作,像这样:
```swift
var optionalValue: Int? = nil
let defaultValue = 10
let result = optionalValue ?? defaultValue
```
在这个例子中,如果 `optionalValue` 是 nil,`result` 将会是 `defaultValue`,否则它将会是 `optionalValue` 解包后的值。
巧用
多个?? 一起使用
a??b??c
a规则不变,c到n与b规则相同
??跟if let 配合使用:if let c = a ?? b {}
是什么
如果包含就自动解包,把值赋值给一个临时的常量(let)或者变量(var),并返回true,否在返回false。
隐式解包(Implicitly unwrapped optional)
在某些情况下,可选项一旦被设定值之后,就会一直用有值,在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为它确定每次访问的是时候都有值
可以再类型后面加一个感叹号!定义一个隐式解包的可选项
语法
类型后边加一个感叹号!
字符串插值与解包
隐式解包
\(age!)
String 描述方法
\(String(describing:age))
空合并运算符
\(age ?? 0)
多重可选项
oh no ,bye bye,拒绝套娃🪆
例子
生僻
done:使用查看内存结构的指令查看不同看是否可以.(fr v -R)
关于swift可选项(?)小技巧:
断点场景下,可用如下lldb命令 : “fr v -R 属性名” 查看可选项结构。 便于调试时查看可选项。(普通可选项、强制可选项、多可选项)
参考辅助图解
var num1: Int? = nil
var num2: Int?? = num1
var num3: Int?? = nil
print(num2 == num3) // false
(num2 ?? 1) ?? 2 // 2
(num3 ?? 1) ?? 2 // 1
var num2: Int?? = num1
var num3: Int?? = nil
print(num2 == num3) // false
(num2 ?? 1) ?? 2 // 2
(num3 ?? 1) ?? 2 // 1
(lldb) fr v -R num1
(Swift.Optional<Swift.Int>) num1 = none {
some = {
_value = 0
}
}
(Swift.Optional<Swift.Int>) num1 = none {
some = {
_value = 0
}
}
(lldb) fr v -R num2
(Swift.Optional<Swift.Optional<Swift.Int>>) num2 = some {
some = none {
some = {
_value = 0
}
}
}
(Swift.Optional<Swift.Optional<Swift.Int>>) num2 = some {
some = none {
some = {
_value = 0
}
}
}
(lldb) fr v -R num3
(Swift.Optional<Swift.Optional<Swift.Int>>) num3 = none {
some = some {
some = {
_value = 0
}
}
}
(Swift.Optional<Swift.Optional<Swift.Int>>) num3 = none {
some = some {
some = {
_value = 0
}
}
}
06、结构体和类
结构体
背景
在swift标准库中,绝大多数的公开类型都是结构体,而枚举和类只占很小一部分。
比如:Bool、Int、Double、String、Array、Dictionary等产检类型都是结构体
声明
自动化生成初始化器
所有结构体都有一个编译器自动生成的初始化器(initializer,初始化方法、构造器、构造方法)
特点:编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是:保证所有成员都有初始化值。
多个初始化器
可选项有默认值nil,因此在自动创建初始化器的时候会依据有默认值来判定,创建多个初始化器。
可选项与自动创建初始化器
自定义初始化器
背景
一旦在定义结构体时自定义了初始化器,编译器就不会再帮它自动生成其他初始化器
举例
struct Date {
var year: Int
var month:Int
var day: Int
}
var date = Date(year:2019, month:6, day:23)
最后一行使用自动生成的初始化器,初始化所有与成员(存储属性)。
var year: Int
var month:Int
var day: Int
}
var date = Date(year:2019, month:6, day:23)
最后一行使用自动生成的初始化器,初始化所有与成员(存储属性)。
内存结构
查看方法
struct Point {
var x: Int = 0
var y: Int = 0
var origin: Bool = false
}
print(MemoryLayout<Point>.size)//17
print(MemoryLayout<Point>.stride)//24
print(MemoryLayout<Point>.alignment)//8
var x: Int = 0
var y: Int = 0
var origin: Bool = false
}
print(MemoryLayout<Point>.size)//17
print(MemoryLayout<Point>.stride)//24
print(MemoryLayout<Point>.alignment)//8
类
初始化器的背景
类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器。
只有类的所有成员都在定义的时候指定了初始值,编译器会为类自动创建无参的初始化器。传参初始化器,需要开发人员手动创建。
便于记忆
无参初始化器
只有当类的所有成员都有初始值时,编译器会自动创建。
可以手动创建无参初始化器吗?可以
在 Swift 中,类与结构体和枚举不同,它们有一些额外的初始化器行为。Swift 中的类如果没有定义任何初始化器,编译器会为你自动生成一个无参的 designated 初始化器。但是,你也可以手动创建无参初始化器。
下面是一个示例,演示了如何手动创建一个无参初始化器:
```swift
class MyClass {
var value: Int
// 手动创建无参初始化器
init() {
value = 0
}
}
// 使用无参初始化器创建实例
let myInstance = MyClass()
print(myInstance.value) // 输出: 0
```
在这个示例中,`MyClass` 类具有一个存储属性 `value`,并且我们手动创建了一个无参初始化器来初始化这个属性。
下面是一个示例,演示了如何手动创建一个无参初始化器:
```swift
class MyClass {
var value: Int
// 手动创建无参初始化器
init() {
value = 0
}
}
// 使用无参初始化器创建实例
let myInstance = MyClass()
print(myInstance.value) // 输出: 0
```
在这个示例中,`MyClass` 类具有一个存储属性 `value`,并且我们手动创建了一个无参初始化器来初始化这个属性。
有参初始化器
编译器不会自动创建,需要自己手动创建。
类的初始化器的使用
类中的属性、方法、下标分为实例、类、静态三种
静态属性、方法、下标的访问方法和类的属性、方法、下标一样
结构体和类的本质区别
类型不同,结构体是值类型,类是引用类型
如图
值类型和引用类型
值类型
赋值和传参操作
介绍
值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份
理解:类似于对文件进行copy、paste操作,产生了全新的文件副本。属于深拷贝(deep copy)
赋值操作
在swift标准库中,为了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技术
比如仅当有“写”操作时,才会真正的执行拷贝操作
对于标准库值类型的赋值操作,swift能确保最佳性能,所以没必要为了保证最佳性能类避免赋值。
建议
不需要修改的,尽量定义成let
本质
单个变量多次赋值
一个变量赋值给另外一个变量
引用类型
背景
引用赋值给var、let或者给函数传参,是将内存地址拷贝一份
类似于制作文件的替身(快捷方式),指向的是同意文件。属于浅拷贝(shallow copy)
赋值操作
一个变量赋值给另一个变量
图解
对象的堆空间申请过程
赋值操作
例子
图解
值类型和引用类型的let
图解
嵌套类型
struct 嵌套枚举
枚举、结构体、类都可以定义方法
这个酷,尤其是枚举增强,有利于减少代码的冗余
背景
一般把定义在枚举、结构体、类内部的函数,叫做方法。
问与答
对象的方法占用内存吗
方法的本质就是函数
方法、函数都存放在代码段
不占用
对象的方法存储在哪里
在 Swift 中,类中的方法存储在虚函数表 (VTable) 中。VTable 是一张方法地址表,它包含了类中所有虚方法的地址。当调用一个虚方法时,编译器会根据对象的类型查找 VTable 中对应的方法地址,然后跳转到该地址执行方法。
具体来说,在 Swift 中,类的方法存储位置如下:
* **实例方法:**实例方法存储在类的 VTable 中。VTable 是一个指针数组,每个元素指向一个实例方法的地址。当调用一个实例方法时,编译器会根据对象的类型查找 VTable 中对应的方法地址,然后跳转到该地址执行方法。
* **类方法:**类方法存储在类的元类 (metaclass) 的 VTable 中。元类是类的类,它也是一个类对象。类方法的调用方式与实例方法类似,只不过编译器会查找元类的 VTable 而不是对象的 VTable。
* **静态方法:**静态方法存储在代码段中。静态方法不属于任何类或对象,可以直接通过类名或方法名调用。
以下是一些关于 Swift 中类方法存储位置的额外信息:
* **final 修饰的方法:**final 修饰的方法是静态方法,它们存储在代码段中。
* **@objc dynamic 修饰的方法:**@objc dynamic 修饰的方法是动态方法,它们存储在 VTable 中。
* **扩展中的方法:**扩展中的方法不存储在 VTable 中,它们是静态方法,存储在代码段中。
希望这些信息对您有所帮助。
具体来说,在 Swift 中,类的方法存储位置如下:
* **实例方法:**实例方法存储在类的 VTable 中。VTable 是一个指针数组,每个元素指向一个实例方法的地址。当调用一个实例方法时,编译器会根据对象的类型查找 VTable 中对应的方法地址,然后跳转到该地址执行方法。
* **类方法:**类方法存储在类的元类 (metaclass) 的 VTable 中。元类是类的类,它也是一个类对象。类方法的调用方式与实例方法类似,只不过编译器会查找元类的 VTable 而不是对象的 VTable。
* **静态方法:**静态方法存储在代码段中。静态方法不属于任何类或对象,可以直接通过类名或方法名调用。
以下是一些关于 Swift 中类方法存储位置的额外信息:
* **final 修饰的方法:**final 修饰的方法是静态方法,它们存储在代码段中。
* **@objc dynamic 修饰的方法:**@objc dynamic 修饰的方法是动态方法,它们存储在 VTable 中。
* **扩展中的方法:**扩展中的方法不存储在 VTable 中,它们是静态方法,存储在代码段中。
希望这些信息对您有所帮助。
swift 分别讲解下实例方法、类方法、静态方法,并谈谈他们的区别是什么,以及存储的位置是否相同
## Swift 实例方法、类方法和静态方法
**1. 实例方法**
**定义:** 实例方法属于类的**实例**,而非类本身。这意味着实例方法必须**依附于类的实例**才能被调用。
**语法:**
```swift
class <类名> {
// 实例方法
func <方法名>(参数列表) -> 返回值类型 {
// 方法体
}
}
```
**特点:**
* 可以访问**实例属性**和**实例方法**
* 可以修改**实例变量**
* 必须通过**类的实例**调用
**示例:**
```swift
class Person {
var name: String
// 实例方法
func sayHello() {
print("Hello, my name is \(name)")
}
}
let person = Person(name: "John")
person.sayHello() // 输出: Hello, my name is John
```
**2. 类方法**
**定义:** 类方法属于**类本身**,而非类的实例。这意味着类方法可以直接通过**类名**调用,无需创建类的实例。
**语法:**
```swift
class <类名> {
// 类方法
class func <方法名>(参数列表) -> 返回值类型 {
// 方法体
}
}
```
**特点:**
* 可以访问**类属性**和**类方法**
* **不能**访问**实例属性**和**实例方法**
* 可以修改**类变量**
* 直接通过**类名**调用
**示例:**
```swift
class Person {
static var count = 0
// 类方法
class func createPerson(name: String) -> Person {
count += 1
return Person(name: name)
}
}
let person1 = Person.createPerson(name: "John")
let person2 = Person.createPerson(name: "Jane")
print(Person.count) // 输出: 2
```
**3. 静态方法**
**定义:** 静态方法与类方法类似,也属于**类本身**,而非类的实例。但静态方法**不能**访问**类属性**或**类方法**,只能访问**静态存储**的变量和常量。
**语法:**
```swift
class <类名> {
// 静态方法
static func <方法名>(参数列表) -> 返回值类型 {
// 方法体
}
}
```
**特点:**
* **不能**访问**类属性**、**类方法**或**实例属性**和**实例方法**
* 可以访问**静态存储**的变量和常量
* 直接通过**类名**调用
**示例:**
```swift
class Person {
// 静态方法
static func greet() {
print("Hello from Person class")
}
}
Person.greet() // 输出: Hello from Person class
```
**4. 区别**
| 特性 | 实例方法 | 类方法 | 静态方法 |
|---|---|---|---|
| 调用方式 | 实例对象.方法名 | 类名.方法名 | 类名.方法名 |
| 访问权限 | 实例属性、实例方法 | 类属性、类方法 | 静态存储的变量和常量 |
| 存储位置 | 堆栈 | 元类 | 代码段 |
**5. 总结**
* 实例方法用于操作类的实例
* 类方法用于操作类本身
* 静态方法用于提供与类相关的工具类方法
**注意:**
* 类方法和静态方法**不能**访问**实例属性**和**实例方法**
* 静态方法**不能**访问**类属性**或**类方法**
希望这些信息对您有所帮助。
**1. 实例方法**
**定义:** 实例方法属于类的**实例**,而非类本身。这意味着实例方法必须**依附于类的实例**才能被调用。
**语法:**
```swift
class <类名> {
// 实例方法
func <方法名>(参数列表) -> 返回值类型 {
// 方法体
}
}
```
**特点:**
* 可以访问**实例属性**和**实例方法**
* 可以修改**实例变量**
* 必须通过**类的实例**调用
**示例:**
```swift
class Person {
var name: String
// 实例方法
func sayHello() {
print("Hello, my name is \(name)")
}
}
let person = Person(name: "John")
person.sayHello() // 输出: Hello, my name is John
```
**2. 类方法**
**定义:** 类方法属于**类本身**,而非类的实例。这意味着类方法可以直接通过**类名**调用,无需创建类的实例。
**语法:**
```swift
class <类名> {
// 类方法
class func <方法名>(参数列表) -> 返回值类型 {
// 方法体
}
}
```
**特点:**
* 可以访问**类属性**和**类方法**
* **不能**访问**实例属性**和**实例方法**
* 可以修改**类变量**
* 直接通过**类名**调用
**示例:**
```swift
class Person {
static var count = 0
// 类方法
class func createPerson(name: String) -> Person {
count += 1
return Person(name: name)
}
}
let person1 = Person.createPerson(name: "John")
let person2 = Person.createPerson(name: "Jane")
print(Person.count) // 输出: 2
```
**3. 静态方法**
**定义:** 静态方法与类方法类似,也属于**类本身**,而非类的实例。但静态方法**不能**访问**类属性**或**类方法**,只能访问**静态存储**的变量和常量。
**语法:**
```swift
class <类名> {
// 静态方法
static func <方法名>(参数列表) -> 返回值类型 {
// 方法体
}
}
```
**特点:**
* **不能**访问**类属性**、**类方法**或**实例属性**和**实例方法**
* 可以访问**静态存储**的变量和常量
* 直接通过**类名**调用
**示例:**
```swift
class Person {
// 静态方法
static func greet() {
print("Hello from Person class")
}
}
Person.greet() // 输出: Hello from Person class
```
**4. 区别**
| 特性 | 实例方法 | 类方法 | 静态方法 |
|---|---|---|---|
| 调用方式 | 实例对象.方法名 | 类名.方法名 | 类名.方法名 |
| 访问权限 | 实例属性、实例方法 | 类属性、类方法 | 静态存储的变量和常量 |
| 存储位置 | 堆栈 | 元类 | 代码段 |
**5. 总结**
* 实例方法用于操作类的实例
* 类方法用于操作类本身
* 静态方法用于提供与类相关的工具类方法
**注意:**
* 类方法和静态方法**不能**访问**实例属性**和**实例方法**
* 静态方法**不能**访问**类属性**或**类方法**
希望这些信息对您有所帮助。
元类存储在哪里
元类存储在**代码段**中。代码段是一块连续的内存区域,它包含了应用程序的代码和数据。代码段中的代码可以直接由 CPU 执行。
在 Swift 中,元类是类的类,它也是一个类对象。元类包含类的元数据,例如类的类型信息、类方法、类属性等。
以下是元类存储位置的详细信息:
* **类对象:**类对象存储在堆栈中。堆栈是一块内存区域,用于存储程序的临时数据。类对象在程序运行时创建,并在程序结束时销毁。
* **元类:**元类存储在代码段中。代码段是一块连续的内存区域,它包含了应用程序的代码和数据。代码段中的代码可以直接由 CPU 执行。
**以下是一些示例:**
```swift
class MyClass {
// 类方法
class func classMethod() {
print("This is a class method.")
}
}
// 调用类方法
MyClass.classMethod()
```
在这个示例中,`MyClass` 是一个类,它有一个类方法 `classMethod()`。`MyClass` 的类对象存储在堆栈中,而 `MyClass` 的元类存储在代码段中。
**总结:**
* 类对象存储在堆栈中
* 元类存储在代码段中
希望这些信息对您有所帮助。
在 Swift 中,元类是类的类,它也是一个类对象。元类包含类的元数据,例如类的类型信息、类方法、类属性等。
以下是元类存储位置的详细信息:
* **类对象:**类对象存储在堆栈中。堆栈是一块内存区域,用于存储程序的临时数据。类对象在程序运行时创建,并在程序结束时销毁。
* **元类:**元类存储在代码段中。代码段是一块连续的内存区域,它包含了应用程序的代码和数据。代码段中的代码可以直接由 CPU 执行。
**以下是一些示例:**
```swift
class MyClass {
// 类方法
class func classMethod() {
print("This is a class method.")
}
}
// 调用类方法
MyClass.classMethod()
```
在这个示例中,`MyClass` 是一个类,它有一个类方法 `classMethod()`。`MyClass` 的类对象存储在堆栈中,而 `MyClass` 的元类存储在代码段中。
**总结:**
* 类对象存储在堆栈中
* 元类存储在代码段中
希望这些信息对您有所帮助。
方法查找的过程
## Swift 方法查找过程
**Swift 方法查找**是指在类或对象中找到要调用的方法的过程。方法查找的机制是**动态派发**,这意味着在运行时根据对象的类型确定要调用的方法。
**方法查找过程**
方法查找过程可以分为以下几个步骤:
**1. 编译器查找**
* 编译器会根据方法名查找类或对象的**方法表**。方法表是一个指针数组,每个元素指向一个方法的地址。
* 如果方法名在方法表中找到,则编译器会将该方法的地址放入**指令**中。
**2. 运行时查找**
* 在运行时,CPU 会根据指令中的地址执行方法。
* 如果方法是**实例方法**,则 CPU 会将对象的地址压入堆栈,然后跳转到方法的地址。
* 如果方法是**类方法**,则 CPU 会直接跳转到方法的地址。
**方法查找的细节**
方法查找的细节如下:
* **方法表:**方法表存储在类的**元类**中。元类是类的类,它也是一个类对象。元类包含类的元数据,例如类的类型信息、类方法、类属性等。
* **指令:**指令是 CPU 执行的代码。指令包含要调用的方法的地址。
**方法查找的示例**
以下是一个方法查找的示例:
```swift
class MyClass {
func method() {
print("This is a method in MyClass.")
}
}
let instance = MyClass()
instance.method()
```
在这个示例中,`MyClass` 有一個方法 `method()`。`MyClass` 的方法表存储在 `MyClass` 的元类中。
**编译器查找阶段**
* 编译器会根据方法名 `method()` 查找 `MyClass` 的方法表,并找到方法 `method()` 的地址。
* 编译器将该地址放入指令中。
**运行时查找阶段**
* 在运行时,CPU 会根据指令中的地址执行方法 `method()`。
* 由于 `method()` 是实例方法,CPU 会将对象的地址压入堆栈,然后跳转到方法的地址。
**方法重载**
**方法重载**是指同一个类中可以定义多个具有相同名称但参数不同的方法。在方法重载的情况下,方法查找过程会根据参数列表来确定要调用的方法。
**方法重载的示例**
以下是一个方法重载的示例:
```swift
class MyClass {
func method(x: Int) {
print("This is method with one parameter.")
}
func method(x: Int, y: Int) {
print("This is method with two parameters.")
}
}
let instance = MyClass()
instance.method(x: 1) // 输出: This is method with one parameter.
instance.method(x: 1, y: 2) // 输出: This is method with two parameters.
```
在这个示例中,`MyClass` 定义了两个具有相同名称 `method()` 的方法,但参数列表不同。
**编译器查找阶段**
* 编译器会根据方法名 `method()` 和参数列表查找 `MyClass` 的方法表。
**运行时查找阶段**
* 在运行时,CPU 会根据指令中的地址执行方法 `method()`。
* 由于 `method()` 是重载方法,CPU 会根据参数列表选择要调用的方法。
**总结**
* 方法查找是根据对象的类型在运行时确定要调用的方法的过程。
* 方法查找的机制是动态派发。
* 方法表存储在类的元类中。
* 指令包含要调用的方法的地址。
* 方法重载是指同一个类中可以定义多个具有相同名称但参数不同的方法。
希望这些信息对您有所帮助。
**Swift 方法查找**是指在类或对象中找到要调用的方法的过程。方法查找的机制是**动态派发**,这意味着在运行时根据对象的类型确定要调用的方法。
**方法查找过程**
方法查找过程可以分为以下几个步骤:
**1. 编译器查找**
* 编译器会根据方法名查找类或对象的**方法表**。方法表是一个指针数组,每个元素指向一个方法的地址。
* 如果方法名在方法表中找到,则编译器会将该方法的地址放入**指令**中。
**2. 运行时查找**
* 在运行时,CPU 会根据指令中的地址执行方法。
* 如果方法是**实例方法**,则 CPU 会将对象的地址压入堆栈,然后跳转到方法的地址。
* 如果方法是**类方法**,则 CPU 会直接跳转到方法的地址。
**方法查找的细节**
方法查找的细节如下:
* **方法表:**方法表存储在类的**元类**中。元类是类的类,它也是一个类对象。元类包含类的元数据,例如类的类型信息、类方法、类属性等。
* **指令:**指令是 CPU 执行的代码。指令包含要调用的方法的地址。
**方法查找的示例**
以下是一个方法查找的示例:
```swift
class MyClass {
func method() {
print("This is a method in MyClass.")
}
}
let instance = MyClass()
instance.method()
```
在这个示例中,`MyClass` 有一個方法 `method()`。`MyClass` 的方法表存储在 `MyClass` 的元类中。
**编译器查找阶段**
* 编译器会根据方法名 `method()` 查找 `MyClass` 的方法表,并找到方法 `method()` 的地址。
* 编译器将该地址放入指令中。
**运行时查找阶段**
* 在运行时,CPU 会根据指令中的地址执行方法 `method()`。
* 由于 `method()` 是实例方法,CPU 会将对象的地址压入堆栈,然后跳转到方法的地址。
**方法重载**
**方法重载**是指同一个类中可以定义多个具有相同名称但参数不同的方法。在方法重载的情况下,方法查找过程会根据参数列表来确定要调用的方法。
**方法重载的示例**
以下是一个方法重载的示例:
```swift
class MyClass {
func method(x: Int) {
print("This is method with one parameter.")
}
func method(x: Int, y: Int) {
print("This is method with two parameters.")
}
}
let instance = MyClass()
instance.method(x: 1) // 输出: This is method with one parameter.
instance.method(x: 1, y: 2) // 输出: This is method with two parameters.
```
在这个示例中,`MyClass` 定义了两个具有相同名称 `method()` 的方法,但参数列表不同。
**编译器查找阶段**
* 编译器会根据方法名 `method()` 和参数列表查找 `MyClass` 的方法表。
**运行时查找阶段**
* 在运行时,CPU 会根据指令中的地址执行方法 `method()`。
* 由于 `method()` 是重载方法,CPU 会根据参数列表选择要调用的方法。
**总结**
* 方法查找是根据对象的类型在运行时确定要调用的方法的过程。
* 方法查找的机制是动态派发。
* 方法表存储在类的元类中。
* 指令包含要调用的方法的地址。
* 方法重载是指同一个类中可以定义多个具有相同名称但参数不同的方法。
希望这些信息对您有所帮助。
07、闭包
闭包表达式(Closure Expression)
在swift中,可以通过func定义一个函数,也可以用过闭包表达式定义一个函数
func sum(_ v1: Int,_ v2: Int) -> Int {v1+v2}
var fn = { (v1: Int, v2: Int) -> Int in return v1+v2}
简写
尾随闭包
如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性
是什么
尾随闭包是一个被书写在函数调用括号外面的闭包表达式
示例
是逃逸闭包吗
是的
如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要在函数名后边写圆括号
示例
数组排序的示例
忽略参数
闭包(Closure)
定义
一个函数和它所捕获的常量、变量环境组合起来,称为闭包
特点
一般指定义在函数内部的函数
示例
一般它捕获的是最外层函数的局部变量和常量
理解
可以把闭包想想成一个类的实例对象
内存在堆空间
捕获的局部变量和常量就是对象的成员,即存储属性
组成闭包的函数就是类内部定义的方法
示例
用来做什么
作为参数
作为返回值
身为函数类型作为函数返回值
方法内部函数作为返回值被返回(使用的闭包类型)
自动闭包
当我们谈到自动闭包时,我们通常是在谈论一种延迟执行的闭包。这意味着闭包内部的代码并不会立即执行,而是在闭包被调用时才会执行。这对于某些情况下很有用,特别是当我们需要传递一个代码块给函数,但是不希望立即执行这个代码块的时候。
在Swift中,我们可以使用 `@autoclosure` 属性来创建自动闭包。这个属性的作用是将传递给函数的表达式自动封装成一个闭包,而不需要显式地编写闭包语法。
考虑下面这个示例:
```swift
func evaluate(condition: @autoclosure () -> Bool) {
if condition() {
print("条件为真")
} else {
print("条件为假")
}
}
```
在这个示例中,`evaluate` 函数接受一个参数 `condition`,这个参数是一个自动闭包,它的返回类型是 `Bool`。当我们调用 `evaluate` 函数时,传递给它的参数会被自动封装成一个闭包。然后,这个闭包在函数内部被调用,从而实现了延迟执行。
这里是一个使用示例:
```swift
evaluate(condition: 2 > 1)
```
在这个调用中,表达式 `2 > 1` 被自动封装成了一个闭包,然后传递给了 `evaluate` 函数。当 `condition()` 被调用时,闭包内部的代码 `2 > 1` 才会执行,结果是 `true`,所以打印出了 "条件为真"。
自动闭包通常在需要延迟执行代码块的场景中使用,比如在某些条件满足时才执行某些操作,或者在性能要求较高的情况下避免不必要的计算。
在Swift中,我们可以使用 `@autoclosure` 属性来创建自动闭包。这个属性的作用是将传递给函数的表达式自动封装成一个闭包,而不需要显式地编写闭包语法。
考虑下面这个示例:
```swift
func evaluate(condition: @autoclosure () -> Bool) {
if condition() {
print("条件为真")
} else {
print("条件为假")
}
}
```
在这个示例中,`evaluate` 函数接受一个参数 `condition`,这个参数是一个自动闭包,它的返回类型是 `Bool`。当我们调用 `evaluate` 函数时,传递给它的参数会被自动封装成一个闭包。然后,这个闭包在函数内部被调用,从而实现了延迟执行。
这里是一个使用示例:
```swift
evaluate(condition: 2 > 1)
```
在这个调用中,表达式 `2 > 1` 被自动封装成了一个闭包,然后传递给了 `evaluate` 函数。当 `condition()` 被调用时,闭包内部的代码 `2 > 1` 才会执行,结果是 `true`,所以打印出了 "条件为真"。
自动闭包通常在需要延迟执行代码块的场景中使用,比如在某些条件满足时才执行某些操作,或者在性能要求较高的情况下避免不必要的计算。
自动闭包
// 如果第1个数大于0,返回第一个数。否则返回第2个数
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
return v1 > 0 ? v1 : v2 }
getFirstPositive(10, 20) // 10 getFirstPositive(-2, 20) // 20 getFirstPositive(0, -4) // -4
// 改成函数类型的参数,可以让v2延迟加载
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int? {
return v1 > 0 ? v1 : v2() }
getFirstPositive(-4) { 20 }
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? { return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4, 20)
n 为了避免与期望冲突,使用了@autoclosure的地方最好明确注释清楚:这个值会被推迟执行
@autoclosure 会自动将 20 封装成闭包 { 20 }
@autoclosure 只支持 () -> T 格式的参数 n@autoclosure 并非只支持最后1个参数
空合并运算符 ?? 使用了 @autoclosure 技术
有@autoclosure、无@autoclosure,构成了函数重载
// 如果第1个数大于0,返回第一个数。否则返回第2个数
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
return v1 > 0 ? v1 : v2 }
getFirstPositive(10, 20) // 10 getFirstPositive(-2, 20) // 20 getFirstPositive(0, -4) // -4
// 改成函数类型的参数,可以让v2延迟加载
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int? {
return v1 > 0 ? v1 : v2() }
getFirstPositive(-4) { 20 }
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? { return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4, 20)
n 为了避免与期望冲突,使用了@autoclosure的地方最好明确注释清楚:这个值会被推迟执行
@autoclosure 会自动将 20 封装成闭包 { 20 }
@autoclosure 只支持 () -> T 格式的参数 n@autoclosure 并非只支持最后1个参数
空合并运算符 ?? 使用了 @autoclosure 技术
有@autoclosure、无@autoclosure,构成了函数重载
08、属性
swift中实例相关的属性可以分为两大类
按照是否开辟空间用于存储分类
存储属性
背景
类似于成员变量这个概念
存储在实例的内存中
结构体、类可以定义存储属性,枚举不可以定义存储属性
规则
swift规定在创建类或者结构体的实例时,必须为所有存储属性设置一个合适的初始值。
可以在初始化器里为存储属性设置初始值
也可以分配一个默认的属性值作为属性定义的一部分
分类
非延迟存储属性
延迟存储属性
表达
使用lazy可以定义一个延迟存储属性
特点
延迟初始化
在第一次用到属性的时候才会进行初始化
规则
lazy属性必须是var,不能是let
let必须在实例初始化方法完成之前就拥有值
不可以设置属性观察器
坑
如果多条线程同时第一次访问lazy属性时,无法保证属性只被初始化1次
注意点
当结构体包含一个延迟存储属性时,只有var才能访问延迟存储属性
为什么
因为延迟属性初始化时需要改变结构体的内存
示例
计算属性
背景
本质就是方法
不占用实例的内存
枚举、结构体、类都可以定义计算属性
规则
set传入新值默认叫做newValue,也可以自定义
只读属性只有get,没有set
定义计算属性智能用var,不能用let
为什么
计算属性的值是可能发生变化,所以不可以使用let修饰
枚举rawValue原理
枚举原始值rawValue的本质是:只读计算属性
按照实例和类型属性进行分类
实例属性
只能通过实例去访问
分为
存储实例属性
存储在实例内存中,每个实例都有一份
计算实例属性
类型属性
只能通过类型去访问
分类
存储类型属性
整个程序运行过程中,就只有一份内存(类似全局变量)
计算类型属性
表达
可以通过static定义类型属性
如果是类,也可以用关键字class
细节
不同于实例属性,你必须给存储类型属性设置初始值
因为类型没有相实例那样的init初始化器来初始化存储属性
存储类型属性默认就是lazy。在第一次使用的时候才初始化,就算被多个线程同时访问,保证只会初始化一次。
为什么?
储存类型属性可以是let
枚举、结构体、类均可定义类型属性
属性观察器(property Observer)
示例
规则
willSet会传递新值,默认叫newValue
didSet会传递旧值,默认叫oldValue
在初始化器中设置属性值不会触发willSet和didSet
这个很重要,这个很重要
在属性定义时设置初始化值也不会触发willSet和didSet
这个很重要,这个很重要
全局变量和局部变量
属性观察器、计算属性的功能,同样可以应用在全局变量、局部变量身上
这个威武这个帅呆了
inout的再次研究
会触发属性观察器
本质
总结:inout的本质就是引用传递(地址传递)
如果实参有物理内存地址,且没有设置属性观察器,直接将示例的内存地址传入函数(实参进行引用传递)
如果实参是计算属性或者设置了属性观察器,采取了copy In copy out的做法
调用函数时,先复制实参的值,产生副本【get】
将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
函数返回后,再将副本的值覆盖实参的值【set】
疑问,todo:产生了副本是什么意思,开辟新的空间了吗?
属性都可以设置观察器吗?不是的
实例属性
可以直接设置属性观察器,不过如果是lazy的var存储属性不可以
类属性、静态属性
类属性、静态属性不可以直接设置属性观察器
不可以直接设置属性观察器,那我们如果想观察属性的改变怎么办呢?静态或类计算属性间接设置属性观察器。
实例计算属性间接设置属性观察器。
操作静态和类属性同时添加属性观察器。
在Swift中,类属性(包括静态属性和类属性)是不能直接设置属性观察器的。属性观察器(`willSet` 和 `didSet`)只能用于实例属性。
如果你想要在类属性的值发生改变时执行某些操作,你可以使用计算属性和存储属性的组合,利用其中一个来存储值,另一个来提供属性观察器的功能。例如,你可以使用类方法来修改类属性,并在类方法中实现属性观察器的功能。
下面是一个示例,演示了如何通过类方法修改类属性,并在类方法中实现属性观察器的功能:
```swift
class MyClass {
private static var _myClassProperty: Int = 0
static var myClassProperty: Int {
get {
return _myClassProperty
}
set {
// 在设置新值之前执行 willSet 观察器的功能
willSet {
print("Will set new value for myClassProperty: \(newValue)")
}
_myClassProperty = newValue
// 在设置新值之后执行 didSet 观察器的功能
didSet {
print("Did set new value for myClassProperty: \(myClassProperty)")
}
}
}
static func updateClassProperty(newValue: Int) {
myClassProperty = newValue
}
}
MyClass.updateClassProperty(newValue: 10)
// 输出:
// Will set new value for myClassProperty: 10
// Did set new value for myClassProperty: 10
```
在上面的示例中,`MyClass` 类有一个私有的存储属性 `_myClassProperty` 用于存储类属性的值,然后通过 `myClassProperty` 计算属性来提供对外的访问接口,并在其中实现了属性观察器的功能。同时,类方法 `updateClassProperty` 用于修改类属性的值。
操作静态和类属性同时添加属性观察器。
在Swift中,类属性(包括静态属性和类属性)是不能直接设置属性观察器的。属性观察器(`willSet` 和 `didSet`)只能用于实例属性。
如果你想要在类属性的值发生改变时执行某些操作,你可以使用计算属性和存储属性的组合,利用其中一个来存储值,另一个来提供属性观察器的功能。例如,你可以使用类方法来修改类属性,并在类方法中实现属性观察器的功能。
下面是一个示例,演示了如何通过类方法修改类属性,并在类方法中实现属性观察器的功能:
```swift
class MyClass {
private static var _myClassProperty: Int = 0
static var myClassProperty: Int {
get {
return _myClassProperty
}
set {
// 在设置新值之前执行 willSet 观察器的功能
willSet {
print("Will set new value for myClassProperty: \(newValue)")
}
_myClassProperty = newValue
// 在设置新值之后执行 didSet 观察器的功能
didSet {
print("Did set new value for myClassProperty: \(myClassProperty)")
}
}
}
static func updateClassProperty(newValue: Int) {
myClassProperty = newValue
}
}
MyClass.updateClassProperty(newValue: 10)
// 输出:
// Will set new value for myClassProperty: 10
// Did set new value for myClassProperty: 10
```
在上面的示例中,`MyClass` 类有一个私有的存储属性 `_myClassProperty` 用于存储类属性的值,然后通过 `myClassProperty` 计算属性来提供对外的访问接口,并在其中实现了属性观察器的功能。同时,类方法 `updateClassProperty` 用于修改类属性的值。
09、方法
背景
枚举、结构体、类都可以定义实例方法、类型方法、静态方法吗
结论:
记:枚举、结构、类,321实静类
记忆
枚举:实
结构体:实、静
类:实、静、类
枚举只能定义实例方法。
结构体可以定义实例方法和静态方法。
类可以定义实例方法、类型方法(类方法)和静态方法。
在 Swift 中,枚举(Enumerations)、结构体(Structures)和类(Classes)的方法定义有其特定的规则和限制。以下是关于它们如何定义实例方法、类型方法(也称为类方法)和静态方法的概述:
枚举(Enumerations)
枚举在 Swift 中可以定义关联值和方法。但是,枚举只能定义实例方法,不能定义类型方法或静态方法。这是因为枚举实例化的每个值都是枚举的一个独特实例,所以方法应该与特定实例关联。
结构体(Structures)
结构体可以定义实例方法和静态方法。由于结构体是值类型,所以每个结构体实例都有自己独立的拷贝和关联的方法。结构体不支持类型方法(类方法),因为类型方法通常与类(引用类型)的共享状态和行为相关联。
类(Classes)
类是 Swift 中的引用类型,它们可以定义实例方法、类型方法(类方法)和静态方法。
实例方法:与类的特定实例相关联的方法。它们通过实例来调用,并且可以访问和修改实例的属性。
类型方法(类方法):使用 class 关键字定义的方法,它们与类本身相关联,而不是类的特定实例。类型方法通过类名来调用,通常用于执行与类本身相关的操作,而不是与特定实例相关的操作。
静态方法:在 Swift 中,静态方法实际上是类型方法的同义词。你可以使用 static 关键字代替 class 关键字来定义类型方法。它们的行为和用途与类型方法相同。
需要注意的是,虽然结构体和类都支持静态方法,但由于结构体是值类型,静态方法通常用于执行与结构体类型本身相关的操作,而不是修改结构体实例的状态。而类的静态方法则可能用于执行与类相关的全局操作或提供工具函数。
记:枚举、结构、类,321实静类
记忆
枚举:实
结构体:实、静
类:实、静、类
枚举只能定义实例方法。
结构体可以定义实例方法和静态方法。
类可以定义实例方法、类型方法(类方法)和静态方法。
在 Swift 中,枚举(Enumerations)、结构体(Structures)和类(Classes)的方法定义有其特定的规则和限制。以下是关于它们如何定义实例方法、类型方法(也称为类方法)和静态方法的概述:
枚举(Enumerations)
枚举在 Swift 中可以定义关联值和方法。但是,枚举只能定义实例方法,不能定义类型方法或静态方法。这是因为枚举实例化的每个值都是枚举的一个独特实例,所以方法应该与特定实例关联。
结构体(Structures)
结构体可以定义实例方法和静态方法。由于结构体是值类型,所以每个结构体实例都有自己独立的拷贝和关联的方法。结构体不支持类型方法(类方法),因为类型方法通常与类(引用类型)的共享状态和行为相关联。
类(Classes)
类是 Swift 中的引用类型,它们可以定义实例方法、类型方法(类方法)和静态方法。
实例方法:与类的特定实例相关联的方法。它们通过实例来调用,并且可以访问和修改实例的属性。
类型方法(类方法):使用 class 关键字定义的方法,它们与类本身相关联,而不是类的特定实例。类型方法通过类名来调用,通常用于执行与类本身相关的操作,而不是与特定实例相关的操作。
静态方法:在 Swift 中,静态方法实际上是类型方法的同义词。你可以使用 static 关键字代替 class 关键字来定义类型方法。它们的行为和用途与类型方法相同。
需要注意的是,虽然结构体和类都支持静态方法,但由于结构体是值类型,静态方法通常用于执行与结构体类型本身相关的操作,而不是修改结构体实例的状态。而类的静态方法则可能用于执行与类相关的全局操作或提供工具函数。
结论:
记忆:
- 枚举:实
- 结构体:实、静
- 类:实、静、类
- 枚举:只能定义实例方法。
- 结构体:可以定义实例方法、静态方法。
- 类:可以定义实例方法、静态方法、类型方法(类方法)。
在 Swift 中,枚举(Enumerations)、结构体(Structures)和类(Classes)的方法定义有其特定的规则和限制。以下是关于它们如何定义实例方法、类型方法(也称为类方法)和静态方法的概述:
### 枚举(Enumerations)
枚举在 Swift 中可以定义关联值和方法。但是,枚举只能定义实例方法,不能定义类型方法或静态方法。这是因为枚举实例化的每个值都是枚举的一个独特实例,所以方法应该与特定实例关联。
### 结构体(Structures)
结构体可以定义实例方法和静态方法。由于结构体是值类型,所以每个结构体实例都有自己独立的拷贝和关联的方法。结构体不支持类型方法(类方法),因为类型方法通常与类(引用类型)的共享状态和行为相关联。
### 类(Classes)
类是 Swift 中的引用类型,它们可以定义实例方法、类型方法(类方法)和静态方法。
- **实例方法**:与类的特定实例相关联的方法。它们通过实例来调用,并且可以访问和修改实例的属性。
- **类型方法(类方法)**:使用 `class` 关键字定义的方法,它们与类本身相关联,而不是类的特定实例。类型方法通过类名来调用,通常用于执行与类本身相关的操作,而不是与特定实例相关的操作。
- **静态方法**:在 Swift 中,静态方法实际上是类型方法的同义词。你可以使用 `static` 关键字代替 `class` 关键字来定义类型方法。它们的行为和用途与类型方法相同。
需要注意的是,虽然结构体和类都支持静态方法,但由于结构体是值类型,静态方法通常用于执行与结构体类型本身相关的操作,而不是修改结构体实例的状态。而类的静态方法则可能用于执行与类相关的全局操作或提供工具函数。
记忆:
- 枚举:实
- 结构体:实、静
- 类:实、静、类
- 枚举:只能定义实例方法。
- 结构体:可以定义实例方法、静态方法。
- 类:可以定义实例方法、静态方法、类型方法(类方法)。
在 Swift 中,枚举(Enumerations)、结构体(Structures)和类(Classes)的方法定义有其特定的规则和限制。以下是关于它们如何定义实例方法、类型方法(也称为类方法)和静态方法的概述:
### 枚举(Enumerations)
枚举在 Swift 中可以定义关联值和方法。但是,枚举只能定义实例方法,不能定义类型方法或静态方法。这是因为枚举实例化的每个值都是枚举的一个独特实例,所以方法应该与特定实例关联。
### 结构体(Structures)
结构体可以定义实例方法和静态方法。由于结构体是值类型,所以每个结构体实例都有自己独立的拷贝和关联的方法。结构体不支持类型方法(类方法),因为类型方法通常与类(引用类型)的共享状态和行为相关联。
### 类(Classes)
类是 Swift 中的引用类型,它们可以定义实例方法、类型方法(类方法)和静态方法。
- **实例方法**:与类的特定实例相关联的方法。它们通过实例来调用,并且可以访问和修改实例的属性。
- **类型方法(类方法)**:使用 `class` 关键字定义的方法,它们与类本身相关联,而不是类的特定实例。类型方法通过类名来调用,通常用于执行与类本身相关的操作,而不是与特定实例相关的操作。
- **静态方法**:在 Swift 中,静态方法实际上是类型方法的同义词。你可以使用 `static` 关键字代替 `class` 关键字来定义类型方法。它们的行为和用途与类型方法相同。
需要注意的是,虽然结构体和类都支持静态方法,但由于结构体是值类型,静态方法通常用于执行与结构体类型本身相关的操作,而不是修改结构体实例的状态。而类的静态方法则可能用于执行与类相关的全局操作或提供工具函数。
分类
实例方法
通过实例对象调用
类型方法
通过类型调用,用class关键字定义
静态方法
通过类型调用,用static关键字定义
思
swift 的静态方法和类方法有什么区别 为什么要有静态方法呢?
静态方法不能被继承,不可被重载
在 Swift 中,静态方法和类方法都与类本身相关联,而不是与类的特定实例相关联。尽管它们有相似之处,但它们在用法和特性上存在一些区别。
静态方法(Static Methods)
静态方法是与类本身相关联的方法,它们可以通过类名直接调用,而不需要创建类的实例。静态方法主要用于执行与类本身相关的操作,而不是与特定实例相关的操作。
在 Swift 中,静态方法实际上是类型方法的同义词,可以使用 static 关键字来定义。静态方法不能被重写,这意味着如果你在子类中定义一个与父类中静态方法同名的方法,那么子类的方法将隐藏父类的静态方法,而不是重写它。
类方法(Type Methods)
类方法也是与类本身相关联的方法,它们也使用类名来调用。在 Swift 中,类方法使用 class 关键字来定义。类方法的一个关键特性是它们可以被重写。这意味着子类可以提供一个与父类类方法同名的新实现,以改变方法的行为。
静态方法和类方法的区别
重写能力:类方法可以被子类重写,而静态方法则不能被重写。
语义差异:虽然静态方法和类方法在功能上相似,但它们的语义略有不同。静态方法更强调其静态性质,即它们不依赖于类的任何特定实例。类方法则更强调它们与类本身的关联,同时提供了重写的灵活性。
为什么要有静态方法
静态方法的存在有几个原因:
方便性:静态方法允许你通过类名直接调用方法,而无需创建类的实例。这在某些情况下非常方便,特别是当你需要执行与类本身相关但不涉及实例状态的操作时。
组织代码:静态方法可以用于组织那些与类本身相关但不依赖于实例状态的功能。这有助于将代码划分为更小的、更易于管理的部分。
性能优化:静态方法通常使用静态派发,这意味着编译器在编译时就能确定调用哪个方法,避免了运行时的查找和解析过程。这可以提高代码的性能。
总的来说,静态方法和类方法都是与类本身相关联的方法,它们在用法和特性上有所不同。静态方法强调其静态性质,而类方法则提供了重写的灵活性。选择使用哪种方法取决于具体的需求和场景。
在 Swift 中,静态方法和类方法都与类本身相关联,而不是与类的特定实例相关联。尽管它们有相似之处,但它们在用法和特性上存在一些区别。
静态方法(Static Methods)
静态方法是与类本身相关联的方法,它们可以通过类名直接调用,而不需要创建类的实例。静态方法主要用于执行与类本身相关的操作,而不是与特定实例相关的操作。
在 Swift 中,静态方法实际上是类型方法的同义词,可以使用 static 关键字来定义。静态方法不能被重写,这意味着如果你在子类中定义一个与父类中静态方法同名的方法,那么子类的方法将隐藏父类的静态方法,而不是重写它。
类方法(Type Methods)
类方法也是与类本身相关联的方法,它们也使用类名来调用。在 Swift 中,类方法使用 class 关键字来定义。类方法的一个关键特性是它们可以被重写。这意味着子类可以提供一个与父类类方法同名的新实现,以改变方法的行为。
静态方法和类方法的区别
重写能力:类方法可以被子类重写,而静态方法则不能被重写。
语义差异:虽然静态方法和类方法在功能上相似,但它们的语义略有不同。静态方法更强调其静态性质,即它们不依赖于类的任何特定实例。类方法则更强调它们与类本身的关联,同时提供了重写的灵活性。
为什么要有静态方法
静态方法的存在有几个原因:
方便性:静态方法允许你通过类名直接调用方法,而无需创建类的实例。这在某些情况下非常方便,特别是当你需要执行与类本身相关但不涉及实例状态的操作时。
组织代码:静态方法可以用于组织那些与类本身相关但不依赖于实例状态的功能。这有助于将代码划分为更小的、更易于管理的部分。
性能优化:静态方法通常使用静态派发,这意味着编译器在编译时就能确定调用哪个方法,避免了运行时的查找和解析过程。这可以提高代码的性能。
总的来说,静态方法和类方法都是与类本身相关联的方法,它们在用法和特性上有所不同。静态方法强调其静态性质,而类方法则提供了重写的灵活性。选择使用哪种方法取决于具体的需求和场景。
在 Swift 中,静态方法和类方法都与类本身相关联,而不是与类的特定实例相关联。尽管它们有相似之处,但它们在用法和特性上存在一些区别。
### 静态方法(Static Methods)
静态方法是与类本身相关联的方法,它们可以通过类名直接调用,而不需要创建类的实例。静态方法主要用于执行与类本身相关的操作,而不是与特定实例相关的操作。
在 Swift 中,静态方法实际上是类型方法的同义词,可以使用 `static` 关键字来定义。静态方法不能被重写,这意味着如果你在子类中定义一个与父类中静态方法同名的方法,那么子类的方法将隐藏父类的静态方法,而不是重写它。
### 类方法(Type Methods)
类方法也是与类本身相关联的方法,它们也使用类名来调用。在 Swift 中,类方法使用 `class` 关键字来定义。类方法的一个关键特性是它们可以被重写。这意味着子类可以提供一个与父类类方法同名的新实现,以改变方法的行为。
### 静态方法和类方法的区别
1. **重写能力**:类方法可以被子类重写,而静态方法则不能被重写。
2. **语义差异**:虽然静态方法和类方法在功能上相似,但它们的语义略有不同。静态方法更强调其静态性质,即它们不依赖于类的任何特定实例。类方法则更强调它们与类本身的关联,同时提供了重写的灵活性。
### 为什么要有静态方法
静态方法的存在有几个原因:
1. **方便性**:静态方法允许你通过类名直接调用方法,而无需创建类的实例。这在某些情况下非常方便,特别是当你需要执行与类本身相关但不涉及实例状态的操作时。
2. **组织代码**:静态方法可以用于组织那些与类本身相关但不依赖于实例状态的功能。这有助于将代码划分为更小的、更易于管理的部分。
3. **性能优化**:静态方法通常使用静态派发,这意味着编译器在编译时就能确定调用哪个方法,避免了运行时的查找和解析过程。这可以提高代码的性能。
总的来说,静态方法和类方法都是与类本身相关联的方法,它们在用法和特性上有所不同。静态方法强调其静态性质,而类方法则提供了重写的灵活性。选择使用哪种方法取决于具体的需求和场景。
### 静态方法(Static Methods)
静态方法是与类本身相关联的方法,它们可以通过类名直接调用,而不需要创建类的实例。静态方法主要用于执行与类本身相关的操作,而不是与特定实例相关的操作。
在 Swift 中,静态方法实际上是类型方法的同义词,可以使用 `static` 关键字来定义。静态方法不能被重写,这意味着如果你在子类中定义一个与父类中静态方法同名的方法,那么子类的方法将隐藏父类的静态方法,而不是重写它。
### 类方法(Type Methods)
类方法也是与类本身相关联的方法,它们也使用类名来调用。在 Swift 中,类方法使用 `class` 关键字来定义。类方法的一个关键特性是它们可以被重写。这意味着子类可以提供一个与父类类方法同名的新实现,以改变方法的行为。
### 静态方法和类方法的区别
1. **重写能力**:类方法可以被子类重写,而静态方法则不能被重写。
2. **语义差异**:虽然静态方法和类方法在功能上相似,但它们的语义略有不同。静态方法更强调其静态性质,即它们不依赖于类的任何特定实例。类方法则更强调它们与类本身的关联,同时提供了重写的灵活性。
### 为什么要有静态方法
静态方法的存在有几个原因:
1. **方便性**:静态方法允许你通过类名直接调用方法,而无需创建类的实例。这在某些情况下非常方便,特别是当你需要执行与类本身相关但不涉及实例状态的操作时。
2. **组织代码**:静态方法可以用于组织那些与类本身相关但不依赖于实例状态的功能。这有助于将代码划分为更小的、更易于管理的部分。
3. **性能优化**:静态方法通常使用静态派发,这意味着编译器在编译时就能确定调用哪个方法,避免了运行时的查找和解析过程。这可以提高代码的性能。
总的来说,静态方法和类方法都是与类本身相关联的方法,它们在用法和特性上有所不同。静态方法强调其静态性质,而类方法则提供了重写的灵活性。选择使用哪种方法取决于具体的需求和场景。
示例
在类型方法static func getCount中,count等价于self.count、Car.self.count、Car.cout
区别
self
在实例方法中代表实例对象自身
在类型方法中代表类型自身
特点
关键字mutating(突变)
结构体和枚举是值类型,默认情况下值类型的属性不能被自身的实例方法修改。
如何解决呢
在func关键字前面加mutating(突变)可以允许这种修改行为
在 Swift 中,mutating 关键字用于在结构体(struct)或枚举(enum)的实例方法中修改它们的值。由于结构体和枚举是值类型,在默认情况下,你不能在它们的方法中直接修改它们的属性,因为方法内部操作的是值的副本。mutating 关键字允许你声明一个方法将修改调用它的结构体或枚举实例本身。
下面是一个使用 mutating 关键字的例子:
swift
复制代码
struct Point {
var x: Int
var y: Int
// 使用 mutating 关键字来声明一个可以修改 Point 实例的方法
mutating func moveBy(dx: Int, dy: Int) {
x += dx
y += dy
}
}
var point = Point(x: 1, y: 1)
print("原始位置: (\(point.x), \(point.y))") // 输出: 原始位置: (1, 1)
// 调用 moveBy 方法来修改 point 实例的 x 和 y 值
point.moveBy(dx: 2, dy: 3)
print("移动后的位置: (\(point.x), \(point.y))") // 输出: 移动后的位置: (3, 4)
在上面的例子中,我们定义了一个名为 Point 的结构体,它有两个属性 x 和 y。我们还定义了一个 moveBy 方法,并使用 mutating 关键字来标记它。这意味着这个方法会修改调用它的 Point 实例的 x 和 y 值。当我们创建一个 Point 实例 point 并调用 moveBy 方法时,point 的 x 和 y 值会被修改。
需要注意的是,mutating 关键字只能用于结构体和枚举的实例方法,而不能用于类(class)的实例方法,因为类是引用类型,它们的实例方法默认就可以修改实例的状态。此外,mutating 也不能用于静态方法或类方法,因为这些方法不与特定的实例关联。
下面是一个使用 mutating 关键字的例子:
swift
复制代码
struct Point {
var x: Int
var y: Int
// 使用 mutating 关键字来声明一个可以修改 Point 实例的方法
mutating func moveBy(dx: Int, dy: Int) {
x += dx
y += dy
}
}
var point = Point(x: 1, y: 1)
print("原始位置: (\(point.x), \(point.y))") // 输出: 原始位置: (1, 1)
// 调用 moveBy 方法来修改 point 实例的 x 和 y 值
point.moveBy(dx: 2, dy: 3)
print("移动后的位置: (\(point.x), \(point.y))") // 输出: 移动后的位置: (3, 4)
在上面的例子中,我们定义了一个名为 Point 的结构体,它有两个属性 x 和 y。我们还定义了一个 moveBy 方法,并使用 mutating 关键字来标记它。这意味着这个方法会修改调用它的 Point 实例的 x 和 y 值。当我们创建一个 Point 实例 point 并调用 moveBy 方法时,point 的 x 和 y 值会被修改。
需要注意的是,mutating 关键字只能用于结构体和枚举的实例方法,而不能用于类(class)的实例方法,因为类是引用类型,它们的实例方法默认就可以修改实例的状态。此外,mutating 也不能用于静态方法或类方法,因为这些方法不与特定的实例关联。
关键字@discardableResult
在func前面加个@discardable,可以消除:函数调用后返回值未被使用的警告⚠️
将方法赋值给var、let
方法也可以像函数那样,赋值给一个let或者var
// 示例:
struct Person {
var age: Int
func run(_ v: Int) { print("func run", age, v) }
static func run(_ v: Int) { print("static func run", v) }
}
let fn1 = Person.run
fn1(10) // static func run 10
let fn2: (Int) -> () = Person.run
fn2(20) // static func run 20
let fn3: (Person) -> ((Int) -> ()) = Person.run
fn3(Person(age: 18))(30) // func run 18 30
struct Person {
var age: Int
func run(_ v: Int) { print("func run", age, v) }
static func run(_ v: Int) { print("static func run", v) }
}
let fn1 = Person.run
fn1(10) // static func run 10
let fn2: (Int) -> () = Person.run
fn2(20) // static func run 20
let fn3: (Person) -> ((Int) -> ()) = Person.run
fn3(Person(age: 18))(30) // func run 18 30
10、下标(subscript)
背景
使用subscript可以给任意类型(枚举、结构体、类)增加下标功能,有些地方也翻译为:下标脚本
subscript的语法类似于实例方法、计算属性,本质就是方法
示例
规则
可以没有set方法,但必须要有get方法
如果只有get方法可以省略get
可以设置参数标签
下标可以是类型方法
结构体、类作为返回值对比
接收多个参数的下标
11、继承(Inheritance)
规则
值类型(枚举、结构体)不支持继承,只有类支持继承
没有父类的类,称为基类
swift并没有像OC、java那样的规定:任何类最终都要继承自某个基类
子类可以重写父类的下标、方法、属性,重写必须加上override关键字
内存结构
示例
看视频
重写
实例方法和下标
类型方法和下标
被class修饰的类型方法、下标,允许被子类重写
被static修饰的类型方法、下标,不允许被子类重写
属性
重写规则
子类可以将父类的属性(存储、计算)重写为计算属性
子类不可以将父类的计算属性重写为存储属性
只能重写var属性,不能重写let属性
重写时,属性名、类型要一致
子类重写后的属性权限,不小于父类属性的权限
如果父类属性是只读的,那么子类重写后的属性可以是只读的、也可以是可读写的
如果父类属性是可读的,那么子类重写后的属性也必须是可读写的
实例属性
示例
类型属性
示例
规则
被class修饰的属性(存储和计算),可以被子类重写。
被static修饰的属性(存储、计算),不可以被子类重写
属性观察器
可以在子类中为父类属性(除了只读计算属性、let属性)增加属性观察器
具有继承属性
子类重写了父类的属性,父类的属性观察器还可以被触发吗
结论:子类重写了父类的属性,修改子类的该属性,父类的属性的观察器不会触发。如果想让父类的观察器触发,需要在子类重写属性是手动调用父类的触发器。
在 Swift 中,如果你重写了父类的属性,并且父类属性的观察器(property observer,比如 willSet 和 didSet)存在,那么这些观察器不会自动在子类属性的值改变时被触发。
子类重写了父类的属性,实际上是在子类层面创建了一个新的属性。这个新的属性与父类的属性是完全独立的,除非你明确地调用父类的属性或在其 willSet 或 didSet 观察器中手动调用它们。
以下是一个例子来阐述这一点:
swift
复制代码
class ParentClass {
var someProperty: Int = 0 {
willSet(newValue) {
print("ParentClass someProperty will be set to \(newValue)")
}
didSet {
print("ParentClass someProperty was set to \(someProperty)")
}
}
}
class ChildClass: ParentClass {
override var someProperty: Int {
get {
return super.someProperty
}
set {
super.someProperty = newValue
}
}
}
let child = ChildClass()
child.someProperty = 10 // 这里不会触发 ParentClass 的观察器
在上面的例子中,ChildClass 重写了 someProperty 属性,并且在其 getter 和 setter 中调用了 super 来访问和设置父类的 someProperty。但是,当我们改变 ChildClass 实例的 someProperty 属性时,ParentClass 中的 willSet 和 didSet 观察器并不会被触发。
如果你希望父类的观察器在子类属性改变时也被触发,你需要在子类的 setter 中显式地调用父类的 setter,或者手动触发父类观察器中的代码。例如:
swift
复制代码
class ChildClass: ParentClass {
override var someProperty: Int {
get {
return super.someProperty
}
set {
willSet { newValue in
// 这里可以手动调用父类的 willSet
super.someProperty.willSet(newValue)
}
didSet {
// 这里可以手动调用父类的 didSet
super.someProperty.didSet
}
super.someProperty = newValue
}
}
}
在这个修改后的 ChildClass 中,我们在子类的 setter 中手动调用了父类的 willSet 和 didSet 观察器。这样,当子类的 someProperty 被设置时,父类的观察器也会被触发。但是请注意,这样做可能会引入额外的复杂性和潜在的副作用,因此在实际编程中应谨慎使用。
在 Swift 中,如果你重写了父类的属性,并且父类属性的观察器(property observer,比如 willSet 和 didSet)存在,那么这些观察器不会自动在子类属性的值改变时被触发。
子类重写了父类的属性,实际上是在子类层面创建了一个新的属性。这个新的属性与父类的属性是完全独立的,除非你明确地调用父类的属性或在其 willSet 或 didSet 观察器中手动调用它们。
以下是一个例子来阐述这一点:
swift
复制代码
class ParentClass {
var someProperty: Int = 0 {
willSet(newValue) {
print("ParentClass someProperty will be set to \(newValue)")
}
didSet {
print("ParentClass someProperty was set to \(someProperty)")
}
}
}
class ChildClass: ParentClass {
override var someProperty: Int {
get {
return super.someProperty
}
set {
super.someProperty = newValue
}
}
}
let child = ChildClass()
child.someProperty = 10 // 这里不会触发 ParentClass 的观察器
在上面的例子中,ChildClass 重写了 someProperty 属性,并且在其 getter 和 setter 中调用了 super 来访问和设置父类的 someProperty。但是,当我们改变 ChildClass 实例的 someProperty 属性时,ParentClass 中的 willSet 和 didSet 观察器并不会被触发。
如果你希望父类的观察器在子类属性改变时也被触发,你需要在子类的 setter 中显式地调用父类的 setter,或者手动触发父类观察器中的代码。例如:
swift
复制代码
class ChildClass: ParentClass {
override var someProperty: Int {
get {
return super.someProperty
}
set {
willSet { newValue in
// 这里可以手动调用父类的 willSet
super.someProperty.willSet(newValue)
}
didSet {
// 这里可以手动调用父类的 didSet
super.someProperty.didSet
}
super.someProperty = newValue
}
}
}
在这个修改后的 ChildClass 中,我们在子类的 setter 中手动调用了父类的 willSet 和 didSet 观察器。这样,当子类的 someProperty 被设置时,父类的观察器也会被触发。但是请注意,这样做可能会引入额外的复杂性和潜在的副作用,因此在实际编程中应谨慎使用。
final
被final修饰的方法、下标、属性禁止被重写
被final修饰的类,禁止被重写
总结
final、static 属性不可被重写。
父类属性有观察器,子类实例该属性改变时,会触发父类属性观察器。如果子类重写了父类的该属性,子类就不会触发,只有重写父类该属性时手动调用才可以触发父类的属性观察器。
父类属性有观察器,子类实例该属性改变时,会触发父类属性观察器。如果子类重写了父类的该属性,子类就不会触发,只有重写父类该属性时手动调用才可以触发父类的属性观察器。
12、初始化
类、结构体、枚举都可以定义初始化器
类有2种初始化器
指定初始化器(designated initializer)
便捷初始化器(convenience initializer)
规则
每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器
默认初始化器总是类的指定初始化器
类偏向于少量指定初始化器,一个类通常只有一个指定初始化器
初始化器的相互调用规则
指定初始化器必须从它的直系父类调用指定初始化器
便捷初始化器必须从相同的类里调用另一个初始化器
便捷初始化器最终必须调用一个指定初始化器
图示
为什么有这样的规则
为了保证使用任意初始化器,都可以完整地初始化实例
两段式初始化(初始化链)(嵌套结构)
第一阶段
初始化所有存储属性
外层调用指定、便捷初始化器
分配内存给实例,但未初始化
指定初始化器确保当前类定义的存储属性都初始化
指定初始化器调用父类的初始化器,不断向上调用,形成初始化链
第二阶段
设置新的存储属性值
从顶部初始化器往下,链中的每一个指定初始化器都要机会进一步初始化定制实例
初始化器现在能够使用self(访问、修改它的属性,调用跳的实例方法等等)
最终链中任何便捷初始化器都有机会定制实例以及使用self
安全检查
指定初始化器必须保证在调用父类初始化器之前,其所在类定义的所有存储属性都要初始化完成
指定初始化器必须先调用父类初始化器,然后才能为继承属性设置新值
便捷初始化器必须先调用同类中的其他初始化器,然后再为任意属性设置新值
初始化器在第一节吨初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self
助记:self、实例属性、实例方法
直到第一阶段完成,实例才算合法
重写
当重写父类的指定初始化器时,必须加上override(即使子类的实现是便捷初始化器)
助记:重写父类的指定初始化器必须加override
如果子类写了一个匹配父类便捷初始化器的初始化器,不用加上override
助记:重写父类的便捷初始化器不用加override
为什么
因为父类的便捷初始化器永远不会通过子类直接调用,因此,严格来说,子类无法重写父类的便捷初始化器
额这个 那为什么还要说这么多呢。。。。不可能的事情就这样吧
自继承
如果子类没有自定义任何指定初始化器,它会自动继承父类所有的指定初始化器
如果子类提供了父类所有指定初始化器的实现(要么通过方式1继承,要么重写)
子类自动继承所有父类的便捷初始化器
就算子类添加了更多的便捷初始化器,这些规则依然适用
子类以便捷初始化器的形式重写父类的指定初始化器,也可以作为满足规则2的一部分
required关键字
用required修饰指定初始化器,表明其所有子类都必须实现该初始化器(通过继承或者重写实现)
如果子类重写了required初始化器,也必须加上required,不用加override
示例
属性观察器
父类属性在它自己的初始化器中赋值不会出发属性观察器,但在子类的初始化器中赋值会触发属性观察器
示例
可失败初始化器
类结构体、枚举都可以使用init?定义可失败初始化器
可以用init!定义隐式解包的可失败初始化器
不允许同时定义参数标签、参数个数、参数类型相同的可失败初始化器和非可失败初始化器
如果初始化器调用一个可失败初始化器导致初始化失败,那么整个初始化过程都失败,并且之后的代码都停止执行
可以用一个非可失败初始化器重写一个可失败初始化器,但反过来是不行的
可失败初始化器可以调用非可失败初始化器,非可失败初始化器调用可失败初始化器需要进行解包
示例
反初始化器
deinit叫做反初始化器,类似c++的析构函数,oc中的dealloc方法
当类的实例对象被释放内存时,就会调用实例对象的deinit方法
dinit不接受任何参数,不能写小括号,不能自行调用
父类deinit能被子类继承
子类的deinit实现执行完毕后会调用父类的deinit
为什么要有两段式初始化和安全检查呢?
swift在编码安全方面是煞费苦心,为了保证初始化过程的安全,设定了两段式初始化、安全检查
13、可选链(Optional Chaining)
是什么
多个?可以链接在一起构成了可选链
如果可选项为空,调用方法、下标、属性失败,结果为nil
如果可选项不为空,调用方法、下标、属性成功,结果会被包装成可选项,如果结果本来就是可选项,不会进行再次包装
如果链中任何一个节点是nil,那么整个链就会调用失败
示例
坑
var num1: Int? = 5
num1? = 10 // Optional(10)
var num2: Int? = nil
num2? = 10 // nil
num1? = 10 // Optional(10)
var num2: Int? = nil
num2? = 10 // nil
满足如上第三个规则
14、协议(Protocol)
规则
协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守
实现多个协议,多个协议之间用逗号隔开
协议中定义方法时不能有默认值参数
默认情况下,协议中定义的内容必须全部都实现
也有办法办到只实现部分内容
可选协议方法使用 @optional 关键字标记,表示该方法的实现可选。
协议中的
属性
协议中定义属性时必须用var关键字
实现协议时的属性权限要不小于协议重定义的属性的权限
协议定义get、set,只能用var存储属性或get、set计算属性去实现
因为有set
协议定义get,用任何属性都可以实现
todo 不明白不明白
示例
类型属性、类型方法、类型下标
swift 协议中可以使用class 关键字定义属性、方法、下标吗?
不可以,在 Swift 协议中不能使用 class 关键字定义属性、方法、下标。
不可以,在 Swift 协议中不能使用 class 关键字定义属性、方法、下标。
不可以,在 Swift 协议中**不能**使用 `class` 关键字定义属性或者方法。
协议只定义了类、结构体和枚举**必须实现**的功能,而 `class` 关键字通常用于定义类属性和类方法。协议中只能使用 `static` 关键字定义类型属性和类方法。
以下是有关在 Swift 协议中定义属性和方法的规则:
* **属性**
* 只能使用 `var` 关键字定义属性,不能使用 `let` 关键字。
* 类型属性必须使用 `static` 关键字作为前缀。
* 必须声明属性是可读的还是可读可写的。
* **方法**
* 实例方法和类方法没有方法体,允许可变参数,但是不能为协议中的方法参数指定默认值。
* 类方法必须使用 `static` 关键字作为前缀。
以下是一个示例,演示了如何在 Swift 协议中定义属性和方法:
```swift
protocol Drawable {
static var title: String { get } // 类型属性
var color: String { get set } // 实例属性
func draw() // 实例方法
static func create(withColor color: String) -> Self // 类方法
}
```
请注意,协议中不能定义构造方法。如果需要在协议中定义构造方法,可以使用 `required` 关键字标记该方法,并将其放在遵守该协议的类、结构体或枚举中实现。
希望这些信息对您有所帮助。
协议只定义了类、结构体和枚举**必须实现**的功能,而 `class` 关键字通常用于定义类属性和类方法。协议中只能使用 `static` 关键字定义类型属性和类方法。
以下是有关在 Swift 协议中定义属性和方法的规则:
* **属性**
* 只能使用 `var` 关键字定义属性,不能使用 `let` 关键字。
* 类型属性必须使用 `static` 关键字作为前缀。
* 必须声明属性是可读的还是可读可写的。
* **方法**
* 实例方法和类方法没有方法体,允许可变参数,但是不能为协议中的方法参数指定默认值。
* 类方法必须使用 `static` 关键字作为前缀。
以下是一个示例,演示了如何在 Swift 协议中定义属性和方法:
```swift
protocol Drawable {
static var title: String { get } // 类型属性
var color: String { get set } // 实例属性
func draw() // 实例方法
static func create(withColor color: String) -> Self // 类方法
}
```
请注意,协议中不能定义构造方法。如果需要在协议中定义构造方法,可以使用 `required` 关键字标记该方法,并将其放在遵守该协议的类、结构体或枚举中实现。
希望这些信息对您有所帮助。
静态属性、静态方法、静态下标
可以使用static关键字定义静态属性、方法、下标
突变方法:(mutating)
作用
只有将协议中的实例方法标记为mutating,才允许结构体、枚举的具体实现修改自身的内存
类在实现方法时不用加mutating,枚举和结构体才需要加mutating
init 初始化器
协议中还可以定义初始化器init
规则
非final类实现时必须加上required
如果从协议实现的初始化器,刚好是重写了父类的指定初始化器,那么这个初始化器必须同时加上required和override
协议中定义的init?、init!
可以用init、init?、init!实现
协议中定义的init,可以用init、init!去实现
协议的继承
语法
语法与类的继承相似,多个协议间用逗号,分隔
一个协议可以继承多个其他协议
问题:协议继承是单继承还是多继承?
单继承
协议组合&:协议 & 协议 & 类
问所未闻,令人惊叹
示例
// 接收同时遵守Livable、Runnable协议、并且是Person或者其子类的实例
func fn3(obj: Person & Livable & Runnable) {}
扩展:
// 接收同时遵守Livable、Runnable协议、并且是Person或者其子类的实例
typealias RealPerson = Person & Livable & Runnable
func fn4(obj: RealPerson) {}
func fn3(obj: Person & Livable & Runnable) {}
扩展:
// 接收同时遵守Livable、Runnable协议、并且是Person或者其子类的实例
typealias RealPerson = Person & Livable & Runnable
func fn4(obj: RealPerson) {}
规则
协议组合中可以包含无数个协议
协议组合中最多可以包含一个类类型
内置协议
caseiterable协议
应用
遍历枚举
让枚举遵守CaseIterable协议,可以实现遍历枚举值
闻所未闻,闻所未闻
示例
customStringConvertible协议 和 customDebugStringConvertible协议
应用
自定义实例打印字符串
遵守CustomStringConvertible、CustormDebugStringConvertible协议,都可以自定义实例的打印字符串
示例
print调用的事CustomStringConvertible协议的description
debugPrint、po调用的是CustomDebugStringConvertible协议的debugDescription
Any、AnyObject类型
swift提供了两种特殊的类型:Any、AnyObject
Any
可以代表任意类型,如枚举、结构体、类,也包含函数类型
AnyObject
可以代表任意类类型
在协议后面写上:AnyObject代表只有类能遵守这个协议
在协议后面写:class也代表只有类能遵守这个协议
示例
类型判断与类型转换
is、as?、as!、as的用法
示例
用来做什么
is用来判断是否是某种类型
as永磊做强制类型转换
元类、元类型
什么是元类型
用来描述类的类型
应用
多态
示例
应用1
示例
ClassX.self、ClassX.Type
ClassX.Type
ClassX.self的类型
ClassX.self
是一个元类型(metadata)的指针,metadata存放着类型相关信息
属于ClassX.Type类型
示例
示例1
示例2
ClassX是一个类
swift隐藏的基类
Swift._SwiftObject
参考源码
https://github.com/apple/swift/blob/master/stdlib/public/runtime/SwiftObject.h
关键字
self与Self
self 代表当前的对象
Self代表当前的类型
Self一般用作返回值类型,限定返回值跟方法调用者必须是统一类型,也可以作为参数类型
示例
示例
Any、AnyClass
在 Swift 中,`AnyClass` 是一个**类型别名**,代表**任何类的类型**。它本质上是一个包装类型,用于表示可以转换为任何类实例的值。
`AnyClass` 与 `Any` 类型密切相关,但两者之间存在细微差别:
* **`Any` 类型**可以表示**任何值类型**,包括类实例、结构体实例、枚举值、函数、元组等。
* **`AnyClass` 类型**只可以表示**类实例**。
换句话说,`Any` 类型的范围更广,可以表示任何值类型,而 `AnyClass` 类型的范围更窄,只可以表示类实例。
`AnyClass` 类型通常用于以下场景:
* **动态分派**:当您需要在不知道具体类型的情况下调用某个方法或属性时,可以使用 `AnyClass` 类型。例如,以下代码使用 `AnyClass` 类型来调用 `description` 方法:
```swift
let object: AnyClass = Person()
let description = object.description // 输出:Person
```
* **类型检查**:您可以使用 `is` 运算符来检查某个值是否为 `AnyClass` 类型。例如,以下代码检查 `object` 是否为 `Person` 类实例:
```swift
let object: AnyClass = Person()
if object is Person {
let person = object as! Person // 强制转换类型
print(person.name) // 输出:John Doe
}
```
* **泛型编程**:在泛型代码中,您可以使用 `AnyClass` 类型作为参数或返回值,以支持任何类类型。例如,以下泛型函数可以接受任意类型的可迭代集合,并返回该集合中所有元素的描述:
```swift
func describe<T: AnyClass>(collection: [T]) -> [String] {
return collection.map { $0.description }
}
```
总而言之,`AnyClass` 类型是一种特殊类型,用于表示任何类的类型。它在动态分派、类型检查和泛型编程等场景中非常有用。
希望这些信息对您有所帮助。如果您还有其他问题,请随时提出。
`AnyClass` 与 `Any` 类型密切相关,但两者之间存在细微差别:
* **`Any` 类型**可以表示**任何值类型**,包括类实例、结构体实例、枚举值、函数、元组等。
* **`AnyClass` 类型**只可以表示**类实例**。
换句话说,`Any` 类型的范围更广,可以表示任何值类型,而 `AnyClass` 类型的范围更窄,只可以表示类实例。
`AnyClass` 类型通常用于以下场景:
* **动态分派**:当您需要在不知道具体类型的情况下调用某个方法或属性时,可以使用 `AnyClass` 类型。例如,以下代码使用 `AnyClass` 类型来调用 `description` 方法:
```swift
let object: AnyClass = Person()
let description = object.description // 输出:Person
```
* **类型检查**:您可以使用 `is` 运算符来检查某个值是否为 `AnyClass` 类型。例如,以下代码检查 `object` 是否为 `Person` 类实例:
```swift
let object: AnyClass = Person()
if object is Person {
let person = object as! Person // 强制转换类型
print(person.name) // 输出:John Doe
}
```
* **泛型编程**:在泛型代码中,您可以使用 `AnyClass` 类型作为参数或返回值,以支持任何类类型。例如,以下泛型函数可以接受任意类型的可迭代集合,并返回该集合中所有元素的描述:
```swift
func describe<T: AnyClass>(collection: [T]) -> [String] {
return collection.map { $0.description }
}
```
总而言之,`AnyClass` 类型是一种特殊类型,用于表示任何类的类型。它在动态分派、类型检查和泛型编程等场景中非常有用。
希望这些信息对您有所帮助。如果您还有其他问题,请随时提出。
15、错误处理
开发过程常见的错误
语法错误
编译期间报错
逻辑错误
运行时错误
可能会导致闪退,一般也叫做异常
自定义运行时错误
swift中可以通过Error协议自定义运行时的错误信息
示例
函数内部通过throw抛出自定义Error,可能会抛出Error的函数必须加上throws声明
示例
需要使用try调用可能会抛出Error的函数
示例
do-catch
可以使用do-catch捕捉Error,抛出Error后,try下一句直到作用域结束的代码都将停止运行
示例
处理Error的两种方法
通过do-catch捕捉Error
不捕捉Error,在当前函数增加throws声明,Error将自动抛给上层函数
如果顶层函数(main函数)依然没有剥啄Error,那么程序将终止
示例
try?、try!
可以使用try!、try?调用可能会抛出Error的函数,这样就不会去处理Error
示例
本质是什么
是try-catch的语法糖
示例
有什么区别
强制解包?或者称之为隐式解包
rethrows
是什么
函数本身不会抛出错误,但调用闭包参数抛出错误,那么它会将错误向上抛
示例
defer
用来做什么
用来定义以任何方式(抛错误、return等)离开代码块前必须要执行的代码
defer语句将延迟至当前作用域结束之前执行
示例
defer语句的执行顺序与定义顺序相反
示例
assert(断言)
很多编程语言都有断言机制
规则
不符合指定条件就抛出运行时错误,常用于调试(Debug)阶段的条件判断
默认情况下,swift断言只会在Debug模式下生效,Release模式下会忽略
增加Swift Flags修改断言的默认行为
-assert-config Release
强制关闭断言
-assert-config Debug
强制开启断言
图解
fatalError
如果遇到严重问题,希望结束程序运行时,可以直接使用fatalError函数抛出错误(这是无法通过do-catch剥啄的错误)
使用fatalError函数,就不需要写return
示例
在某些不得不实现、但不希望别人调用的方法,可以考虑内部使用fatalError函数
示例
局部作用域
可以使用do实现局部作用域
示例
16、泛型(Generics)
作用
类型参数化,提高代码复用率,减少代码量
示例1
厉害了,如此清爽的实现了,同类型对象值互换
func swapValues<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
var i1 = 10
var i2 = 20
swapValues(&i1, &i2)
var d1 = 10.0
var d2 = 20.0
swapValues(&d1, &d2)
struct Date {
var year = 0, month = 0, day = 0
}
var dd1 = Date(year: 2011, month: 9, day: 10) var dd2 = Date(year: 2012, month: 10, day: 11) swapValues(&dd1, &dd2)
func swapValues<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
var i1 = 10
var i2 = 20
swapValues(&i1, &i2)
var d1 = 10.0
var d2 = 20.0
swapValues(&d1, &d2)
struct Date {
var year = 0, month = 0, day = 0
}
var dd1 = Date(year: 2011, month: 9, day: 10) var dd2 = Date(year: 2012, month: 10, day: 11) swapValues(&dd1, &dd2)
示例2
泛型函数赋值给变量
示例
泛型可以和那些类型结合使用呢?
类
// 泛型和类一起使用:实现栈
class Stack<E> {
var elements = [E]()
func push(_ element: E) {
elements.append(element)
}
func pop() -> E { elements.removeLast() }
func top() -> E { elements.last! }
func size() -> Int { elements.count }
}
class SubStack<E> : Stack<E> {}
var stack = Stack<Int>()
stack.push(11)
stack.push(22)
stack.push(33)
print(stack.top()) // 33
print(stack.pop()) // 33
print(stack.pop()) // 22
print(stack.pop()) // 11
print(stack.size()) // 0
class Stack<E> {
var elements = [E]()
func push(_ element: E) {
elements.append(element)
}
func pop() -> E { elements.removeLast() }
func top() -> E { elements.last! }
func size() -> Int { elements.count }
}
class SubStack<E> : Stack<E> {}
var stack = Stack<Int>()
stack.push(11)
stack.push(22)
stack.push(33)
print(stack.top()) // 33
print(stack.pop()) // 33
print(stack.pop()) // 22
print(stack.pop()) // 11
print(stack.size()) // 0
结构体
// 泛型和结构体一起使用:实现栈
struct Stack<E> {
var elements = [E]()
mutating func push(_ element: E) { elements.append(element) }
mutating func pop() -> E { elements.removeLast() }
func top() -> E { elements.last! }
func size() -> Int { elements.count }
}
// 泛型和结构体一起使用:实现栈
struct Stack<E> {
var elements = [E]()
mutating func push(_ element: E) { elements.append(element) }
mutating func pop() -> E { elements.removeLast() }
func top() -> E { elements.last! }
func size() -> Int { elements.count }
}
枚举
// 泛型和枚举一起使用
enum Score<T> {
case point(T)
case grade(String)
}
let score0 = Score<Int>.point(100)
let score1 = Score.point(99)
let score2 = Score.point(99.5)
let score3 = Score<Int>.grade("A")
enum Score<T> {
case point(T)
case grade(String)
}
let score0 = Score<Int>.point(100)
let score1 = Score.point(99)
let score2 = Score.point(99.5)
let score3 = Score<Int>.grade("A")
协议:不可以,那怎么办呢,swift为我们推出了关联类型解决,协议无法使用泛型的问题
关联类型(associated Type)
作用:关联类型的引入解决了协议中无法使用泛型的限制,使其能够更加灵活地定义通用接口。
如何使用呢,示例
protocol Stackable {
associatedtype Element // 关联类型
mutating func push(_ element: Element)
mutating func pop() -> Element
func top() -> Element
func size() -> Int
}
// 协议、关联类型、泛型 一起使用示例
class Stack<E> : Stackable {
// 给关联类型设定真实类型,注释掉下一行仍然可以正常运行,系统会进行类型推断
typealias Element = E
var elements = [E]()
func push(_ element: E) {
elements.append(element) }
func pop() -> E { elements.removeLast() }
func top() -> E { elements.last! }
func size() -> Int { elements.count }
}
// 普通类型关联类型使用示例
class StringStack : Stackable {
// 给关联类型设定真实类型,注释掉下一行仍然可以正常运行,系统会进行类型推断
typealias Element = String
var elements = [String]()
func push(_ element: String) { elements.append(element) }
func pop() -> String { elements.removeLast() }
func top() -> String { elements.last! }
func size() -> Int { elements.count } }
var ss = StringStack()
ss.push("Jack")
ss.push("Rose")
//注释显示的关联类型,仍然可以运行,说明系统会自动推断关联类型。此处的类型推断有点类似声明变量的时候的类型推断
associatedtype Element // 关联类型
mutating func push(_ element: Element)
mutating func pop() -> Element
func top() -> Element
func size() -> Int
}
// 协议、关联类型、泛型 一起使用示例
class Stack<E> : Stackable {
// 给关联类型设定真实类型,注释掉下一行仍然可以正常运行,系统会进行类型推断
typealias Element = E
var elements = [E]()
func push(_ element: E) {
elements.append(element) }
func pop() -> E { elements.removeLast() }
func top() -> E { elements.last! }
func size() -> Int { elements.count }
}
// 普通类型关联类型使用示例
class StringStack : Stackable {
// 给关联类型设定真实类型,注释掉下一行仍然可以正常运行,系统会进行类型推断
typealias Element = String
var elements = [String]()
func push(_ element: String) { elements.append(element) }
func pop() -> String { elements.removeLast() }
func top() -> String { elements.last! }
func size() -> Int { elements.count } }
var ss = StringStack()
ss.push("Jack")
ss.push("Rose")
//注释显示的关联类型,仍然可以运行,说明系统会自动推断关联类型。此处的类型推断有点类似声明变量的时候的类型推断
特性:
协议中可以拥有多个关联类型
可以显示为关联类型赋值,也可以不赋值,由系统自动进行类型推断
协议中可以拥有多个关联类型
可以显示为关联类型赋值,也可以不赋值,由系统自动进行类型推断
泛型类型约束
是什么
是什么:Swift 中的泛型类型约束是一种用于指定泛型类型参数必须满足的条件的机制。
有什么作用:有助于确保泛型代码的类型安全性并使其更具鲁棒性。
**泛型类型约束的语法**
泛型类型约束的语法如下:
```swift
<T: Protocol, TypeRequirement> where T.AssociatedType: AnotherProtocol,
T: Comparable
```
其中:
* `T` 是泛型类型参数。
* `Protocol` 是泛型类型参数必须遵循的协议。
* `TypeRequirement` 是泛型类型参数必须满足的类型要求。
* `AssociatedType` 是泛型类型参数关联类型。
* `AnotherProtocol` 是关联类型必须遵循的协议。
* `Comparable` 是泛型类型参数必须遵循的协议。
**泛型类型约束的示例**
以下是一些泛型类型约束的示例:
* **要求泛型类型参数遵循某个协议:**
```swift
func swapValues<T: Comparable>(_ a: inout T, _ b: inout T) {
// ...
}
```
在这个示例中,`swapValues` 函数的泛型类型参数 `T` 必须遵循 `Comparable` 协议。这意味着 `T` 类型的值可以进行比较。
* **要求泛型类型参数的关联类型遵循某个协议:**
```swift
protocol Stackable {
associatedtype Element: Equatable
}
class Stack<E: Equatable>: Stackable {
typealias Element = E
}
```
在这个示例中,`Stack` 类的泛型类型参数 `E` 必须遵循 `Equatable` 协议。这意味着 `E` 类型的值可以进行相等性比较。
* **要求泛型类型参数满足多个条件:**
```swift
func findIndex<T: StringProtocol & Comparable>(_ array: [T], _ value: T) -> Int? {
// ...
}
```
在这个示例中,`findIndex` 函数的泛型类型参数 `T` 必须遵循 `StringProtocol` 和 `Comparable` 协议。这意味着 `T` 类型的值可以作为字符串处理并且可以进行比较。
**使用泛型类型约束的好处**
使用泛型类型约束可以带来以下好处:
* **提高类型安全性:** 泛型类型约束可以确保泛型代码只用于类型安全的类型。这可以帮助防止运行时错误。
* **提高代码鲁棒性:** 泛型类型约束可以使泛型代码更具鲁棒性,因为它可以处理各种类型的输入。
* **提高代码可读性:** 泛型类型约束可以提高代码的可读性,因为它可以清楚地说明泛型代码的预期用途。
**总结**
泛型类型约束是 Swift 中一种强大的功能,可用于确保泛型代码的类型安全性、鲁棒性和可读性。
有什么作用:有助于确保泛型代码的类型安全性并使其更具鲁棒性。
**泛型类型约束的语法**
泛型类型约束的语法如下:
```swift
<T: Protocol, TypeRequirement> where T.AssociatedType: AnotherProtocol,
T: Comparable
```
其中:
* `T` 是泛型类型参数。
* `Protocol` 是泛型类型参数必须遵循的协议。
* `TypeRequirement` 是泛型类型参数必须满足的类型要求。
* `AssociatedType` 是泛型类型参数关联类型。
* `AnotherProtocol` 是关联类型必须遵循的协议。
* `Comparable` 是泛型类型参数必须遵循的协议。
**泛型类型约束的示例**
以下是一些泛型类型约束的示例:
* **要求泛型类型参数遵循某个协议:**
```swift
func swapValues<T: Comparable>(_ a: inout T, _ b: inout T) {
// ...
}
```
在这个示例中,`swapValues` 函数的泛型类型参数 `T` 必须遵循 `Comparable` 协议。这意味着 `T` 类型的值可以进行比较。
* **要求泛型类型参数的关联类型遵循某个协议:**
```swift
protocol Stackable {
associatedtype Element: Equatable
}
class Stack<E: Equatable>: Stackable {
typealias Element = E
}
```
在这个示例中,`Stack` 类的泛型类型参数 `E` 必须遵循 `Equatable` 协议。这意味着 `E` 类型的值可以进行相等性比较。
* **要求泛型类型参数满足多个条件:**
```swift
func findIndex<T: StringProtocol & Comparable>(_ array: [T], _ value: T) -> Int? {
// ...
}
```
在这个示例中,`findIndex` 函数的泛型类型参数 `T` 必须遵循 `StringProtocol` 和 `Comparable` 协议。这意味着 `T` 类型的值可以作为字符串处理并且可以进行比较。
**使用泛型类型约束的好处**
使用泛型类型约束可以带来以下好处:
* **提高类型安全性:** 泛型类型约束可以确保泛型代码只用于类型安全的类型。这可以帮助防止运行时错误。
* **提高代码鲁棒性:** 泛型类型约束可以使泛型代码更具鲁棒性,因为它可以处理各种类型的输入。
* **提高代码可读性:** 泛型类型约束可以提高代码的可读性,因为它可以清楚地说明泛型代码的预期用途。
**总结**
泛型类型约束是 Swift 中一种强大的功能,可用于确保泛型代码的类型安全性、鲁棒性和可读性。
示例
protocol Runnable { }
class Person { }
func swapValues<T : Person & Runnable>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
protocol Stackable {
associatedtype Element: Equatable
}
class Stack<E : Equatable> : Stackable { typealias Element = E }
func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable {
return false
}
var stack1 = Stack<Int>()
var stack2 = Stack<String>()
// error: requires the types 'Int' and 'String' be equivalent equal(stack1, stack2)
protocol Runnable { }
class Person { }
func swapValues<T : Person & Runnable>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
protocol Stackable {
associatedtype Element: Equatable
}
class Stack<E : Equatable> : Stackable { typealias Element = E }
func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable {
return false
}
var stack1 = Stack<Int>()
var stack2 = Stack<String>()
// error: requires the types 'Int' and 'String' be equivalent equal(stack1, stack2)
协议类型的注意点
如果协议中有关联类型
错误
示例
图示
解决方案
1、使用泛型
2、使用some关键字声明一个不透明类型
some限制只能返回一种类型
不理解,看视频
some
可以用在返回值类型前面
限制只能返回一种类型
也可以用在属性类型前面
作用和协议搭配使用
可选项本质
可选项的本质是enum类型
示例
没看懂,没看懂,看视频,看视频
总结:
协议、关联类型一起使用构建了一个灵活多变的复用体
协议、关联类型一起使用构建了一个灵活多变的复用体
17、String与Array
关于String的思考
swift语言的内存结构:
记忆:可Mach-O、代常全 堆栈动
记忆:可Mach-O、代常全 堆栈动
图示
1个String变量占用多少内存?
总结:
短字符串:栈上,最多 15 个字符,15个字符+1个终止符;
较长字符串(超过 15 个字符):栈+堆,前 8 个字节存储对堆上字符串数据的引用。接下来的 8 个字节存储字符串长度和标志。标志指示字符串是否存储在堆上或栈上,以及有关字符串的其他信息。
## Swift 中 String 变量的内存占用
在 Swift 中,`String` 变量的内存占用取决于字符串的长度以及它是存储在堆上还是栈上。
**短字符串(最多 15 个字符)**
对于短字符串(最多 15 个字符),字符串值直接存储在变量的内存空间中。这意味着变量占用 16 个字节的内存,而不管字符串的实际长度是多少。额外的 1 个字节用于空终止符 (`\0`)。
**较长字符串(超过 15 个字符)**
对于较长字符串(超过 15 个字符),字符串值存储在堆上,变量只存储对堆内存位置的引用。这意味着变量本身只占用 16 个字节的内存,但实际的字符串数据在堆上占用额外的内存。
**String 变量的内存布局**
`String` 变量的内存布局可以如下说明:
```
| 8 bytes | 8 bytes |
|------------------------|------------------------|
| String reference | String length and flags |
|------------------------|------------------------|
```
前 8 个字节存储对堆上字符串数据的引用。接下来的 8 个字节存储字符串长度和标志。标志指示字符串是否存储在堆上或栈上,以及有关字符串的其他信息。
**影响内存使用的因素**
`String` 变量的内存使用量可能会受到以下几个因素的影响:
* **字符串长度:** 较长的字符串将比较短的字符串消耗更多的内存。
* **字符串编码:** 字符串的编码(例如,UTF-8、UTF-16)也会影响内存使用量。
* **字符串不可变性:** 不可变字符串(用 `let` 声明)通常比可变字符串(用 `var` 声明)更具内存效率。
**优化内存使用**
为了优化字符串的内存使用,您可以考虑以下做法:
* **尽可能使用短字符串:** 如果您只需要存储一段短文本,请使用短字符串来避免不必要的内存分配。
* **使用适当的字符串编码:** 根据您的需要选择适当的字符串编码。例如,如果您只存储 ASCII 字符,请使用 UTF-8 编码。
* **尽可能使用不可变字符串:** 尽可能将字符串声明为不可变(`let`),以避免不必要的复制。
总的来说,Swift 的内存管理系统非常高效,并且会自动处理内存分配和释放。但是,了解影响内存使用量的因素可以帮助您编写更具内存效率的代码。
短字符串:栈上,最多 15 个字符,15个字符+1个终止符;
较长字符串(超过 15 个字符):栈+堆,前 8 个字节存储对堆上字符串数据的引用。接下来的 8 个字节存储字符串长度和标志。标志指示字符串是否存储在堆上或栈上,以及有关字符串的其他信息。
## Swift 中 String 变量的内存占用
在 Swift 中,`String` 变量的内存占用取决于字符串的长度以及它是存储在堆上还是栈上。
**短字符串(最多 15 个字符)**
对于短字符串(最多 15 个字符),字符串值直接存储在变量的内存空间中。这意味着变量占用 16 个字节的内存,而不管字符串的实际长度是多少。额外的 1 个字节用于空终止符 (`\0`)。
**较长字符串(超过 15 个字符)**
对于较长字符串(超过 15 个字符),字符串值存储在堆上,变量只存储对堆内存位置的引用。这意味着变量本身只占用 16 个字节的内存,但实际的字符串数据在堆上占用额外的内存。
**String 变量的内存布局**
`String` 变量的内存布局可以如下说明:
```
| 8 bytes | 8 bytes |
|------------------------|------------------------|
| String reference | String length and flags |
|------------------------|------------------------|
```
前 8 个字节存储对堆上字符串数据的引用。接下来的 8 个字节存储字符串长度和标志。标志指示字符串是否存储在堆上或栈上,以及有关字符串的其他信息。
**影响内存使用的因素**
`String` 变量的内存使用量可能会受到以下几个因素的影响:
* **字符串长度:** 较长的字符串将比较短的字符串消耗更多的内存。
* **字符串编码:** 字符串的编码(例如,UTF-8、UTF-16)也会影响内存使用量。
* **字符串不可变性:** 不可变字符串(用 `let` 声明)通常比可变字符串(用 `var` 声明)更具内存效率。
**优化内存使用**
为了优化字符串的内存使用,您可以考虑以下做法:
* **尽可能使用短字符串:** 如果您只需要存储一段短文本,请使用短字符串来避免不必要的内存分配。
* **使用适当的字符串编码:** 根据您的需要选择适当的字符串编码。例如,如果您只存储 ASCII 字符,请使用 UTF-8 编码。
* **尽可能使用不可变字符串:** 尽可能将字符串声明为不可变(`let`),以避免不必要的复制。
总的来说,Swift 的内存管理系统非常高效,并且会自动处理内存分配和释放。但是,了解影响内存使用量的因素可以帮助您编写更具内存效率的代码。
下面2个String变量,底层存储有什么不同?
var str1 = "0123456789"
var str2 = "0123456789ABCDEF"
如果对String进行拼接操作,String变量的存储会发生什么变化?
如果对String进行拼接操作, String变量的存储会发生什么变化?var str1 = "0123456789"
var str2 = "0123456789ABCDEF"
str1.append("ABCDE")
str1.append("F")
str2.append("G")
var str2 = "0123456789ABCDEF"
str1.append("ABCDE")
str1.append("F")
str2.append("G")
从编码到启动APP
图示
dyld_stub_binder(符号延迟绑定)
符号的延迟绑定通过dyld_stub_binder完成
jmpq *0xb31(%rip)格式的汇编指令
占用6个字节
图示
关于Array的思考
1个Array变量占用多少内存?
数组中的数据存放在哪里?
示例
图示
18、高级运算符
溢出运算符(overflow Operator)
Swift的算术运算符出现溢出时会抛出运行时错误
swift有溢出运算符(&+、&-、&*),用来支持溢出运算
示例
&-
255 - (x-1)
&+
(x-1)
&*
255 - (x-1)
运算符重载(Operator Overload)
类、结构体、枚举可以为现有的运算符提供自定义的实现,这个操作叫做:运算符重载
示例
示例2
比较运算符相关的内置协议
Equatable协议:自定义两个实例相等策略
应用:自定义相等运算符
判断两个实例是否等价,一般做法是遵循Equatable协议,重载==运算,或者重载!=运算符
在 Swift 中,判断两个实例是否相等的方法取决于实例的类型:
**值类型**
值类型(例如结构体和枚举)通过其值进行比较。这意味着如果两个值类型实例具有相同的值,则它们被视为相等。
以下是如何比较两个值类型实例是否相等的示例:
```swift
struct Point {
let x: Int
let y: Int
}
let p1 = Point(x: 10, y: 20)
let p2 = Point(x: 10, y: 20)
if p1 == p2 {
print("p1 和 p2 相等")
} else {
print("p1 和 p2 不相等")
}
```
此代码将打印“p1 和 p2 相等”,因为 p1 和 p2 具有相同的值(x = 10,y = 20)。
**引用类型**
引用类型(例如类)通过其引用进行比较。这意味着如果两个引用类型实例引用同一个内存位置,则它们被视为相等。
以下是如何比较两个引用类型实例是否相等的示例:
```swift
class Person {
let name: String
}
let p1 = Person(name: "John")
let p2 = p1
if p1 === p2 {
print("p1 和 p2 引用同一个实例")
} else {
print("p1 和 p2 引用不同的实例")
}
```
此代码将打印“p1 和 p2 引用同一个实例”,因为 p1 和 p2 引用同一个 Person 实例。
**自定义相等性**
对于某些类型,可能需要自定义相等性逻辑。例如,如果您有一个 Person 类,并且您希望将两个 Person 实例视为相等,即使它们的名称不同但 ID 相同,则可以实现 Equatable 协议并重写 == 运算符。
以下是如何为 Person 类实现 Equatable 协议的示例:
```swift
class Person: Equatable {
let name: String
let id: Int
static func ==(lhs: Person, rhs: Person) -> Bool {
return lhs.id == rhs.id
}
}
```
现在,您可以使用 == 运算符比较两个 Person 实例,即使它们的名称不同:
```swift
let p1 = Person(name: "John", id: 123)
let p2 = Person(name: "Jane", id: 123)
if p1 == p2 {
print("p1 和 p2 相等")
} else {
print("p1 和 p2 不相等")
}
```
此代码将打印“p1 和 p2 相等”,因为 p1 和 p2 具有相同的 ID (123)。
**总结**
在 Swift 中,判断两个实例是否相等的方法取决于实例的类型。对于值类型,使用 == 运算符比较其值。对于引用类型,使用 === 运算符比较它们的引用。对于需要自定义相等性逻辑的类型,请实现 Equatable 协议并重写 == 运算符。
**值类型**
值类型(例如结构体和枚举)通过其值进行比较。这意味着如果两个值类型实例具有相同的值,则它们被视为相等。
以下是如何比较两个值类型实例是否相等的示例:
```swift
struct Point {
let x: Int
let y: Int
}
let p1 = Point(x: 10, y: 20)
let p2 = Point(x: 10, y: 20)
if p1 == p2 {
print("p1 和 p2 相等")
} else {
print("p1 和 p2 不相等")
}
```
此代码将打印“p1 和 p2 相等”,因为 p1 和 p2 具有相同的值(x = 10,y = 20)。
**引用类型**
引用类型(例如类)通过其引用进行比较。这意味着如果两个引用类型实例引用同一个内存位置,则它们被视为相等。
以下是如何比较两个引用类型实例是否相等的示例:
```swift
class Person {
let name: String
}
let p1 = Person(name: "John")
let p2 = p1
if p1 === p2 {
print("p1 和 p2 引用同一个实例")
} else {
print("p1 和 p2 引用不同的实例")
}
```
此代码将打印“p1 和 p2 引用同一个实例”,因为 p1 和 p2 引用同一个 Person 实例。
**自定义相等性**
对于某些类型,可能需要自定义相等性逻辑。例如,如果您有一个 Person 类,并且您希望将两个 Person 实例视为相等,即使它们的名称不同但 ID 相同,则可以实现 Equatable 协议并重写 == 运算符。
以下是如何为 Person 类实现 Equatable 协议的示例:
```swift
class Person: Equatable {
let name: String
let id: Int
static func ==(lhs: Person, rhs: Person) -> Bool {
return lhs.id == rhs.id
}
}
```
现在,您可以使用 == 运算符比较两个 Person 实例,即使它们的名称不同:
```swift
let p1 = Person(name: "John", id: 123)
let p2 = Person(name: "Jane", id: 123)
if p1 == p2 {
print("p1 和 p2 相等")
} else {
print("p1 和 p2 不相等")
}
```
此代码将打印“p1 和 p2 相等”,因为 p1 和 p2 具有相同的 ID (123)。
**总结**
在 Swift 中,判断两个实例是否相等的方法取决于实例的类型。对于值类型,使用 == 运算符比较其值。对于引用类型,使用 === 运算符比较它们的引用。对于需要自定义相等性逻辑的类型,请实现 Equatable 协议并重写 == 运算符。
swift为一下类型提供默认的Equatable实现
没有关联类型的枚举
只拥有遵守Equatable协议关联类型的枚举
只拥有遵守Equatable协议存储属性的结构体
引用类型比较存储的地址值是否相等(是否引用着同一个对象,使用恒等运算符===,!==)
Comparable协议:自定义两个实例比较策略
应用
想要比较2个实例的大小,一般做法是:
遵守Comparable协议
重载相应的运算符(<、>、<=、>=、)
自定义运算符(Custom Operator)
如何自定义新的运算符
在全局作用域使用operator进行声明
声明用的关键字
prefix operator
前缀运算符
postfix operator
后缀运算符
infix operator 默认
中缀运算符:优先级组
优先级
precedencegroup 优先级组 {
associativity: 结合性(left\right\none)
higherThan: 比谁的优先级高
lowerThan: 比谁的优先级低
assignment: true代表在可选链操作中拥有跟赋值运算符一样的优先级
}
associativity: 结合性(left\right\none)
higherThan: 比谁的优先级高
lowerThan: 比谁的优先级低
assignment: true代表在可选链操作中拥有跟赋值运算符一样的优先级
}
参考
Apple文档参考: https://developer.apple.com/documentation/swift/swift_sta
ndard_library/operator_declarations
Apple文档参考: https://developer.apple.com/documentation/swift/swift_sta
ndard_library/operator_declarations
https://docs.swift.org/swift- book/ReferenceManual/Declarations.html#ID380
示例
示例1
prefix operator +++
infix operator +- : PlusMinusPrecedence precedencegroup PlusMinusPrecedence {
associativity: none
higherThan: AdditionPrecedence lowerThan: MultiplicationPrecedence assignment: true
}
infix operator +- : PlusMinusPrecedence precedencegroup PlusMinusPrecedence {
associativity: none
higherThan: AdditionPrecedence lowerThan: MultiplicationPrecedence assignment: true
}
示例2
struct Point {
var x: Int, y: Int
static prefix func +++ (point: inout Point) -> Point {
point = Point(x: point.x + point.x, y: point.y + point.y)
return point
}
static func +- (left: Point, right: Point) -> Point {
return Point(x: left.x + right.x, y: left.y - right.y)
}
static func +- (left: Point?, right: Point) -> Point {
print("+-")
return Point(x: left?.x ?? 0 + right.x, y: left?.y ?? 0 - right.y) }
}
struct Person {
var point: Point
}
var person: Person? = nil
person?.point +- Point(x: 10, y: 20)
var x: Int, y: Int
static prefix func +++ (point: inout Point) -> Point {
point = Point(x: point.x + point.x, y: point.y + point.y)
return point
}
static func +- (left: Point, right: Point) -> Point {
return Point(x: left.x + right.x, y: left.y - right.y)
}
static func +- (left: Point?, right: Point) -> Point {
print("+-")
return Point(x: left?.x ?? 0 + right.x, y: left?.y ?? 0 - right.y) }
}
struct Person {
var point: Point
}
var person: Person? = nil
person?.point +- Point(x: 10, y: 20)
19、扩展(Extension)
背景
swift中的扩展,有点类似于OC中的分类(Category)
目的是为了复用,有了继承为什么还要引入扩展呢,非侵入复用扩展
规则
扩展可以为枚举、结构体、类、协议添加新功能
可以添加方法、计算属性、下标、便捷初始化器、嵌套类型、协议等等
计算属性
示例
extension Double {
var km: Double { self * 1000.0 }
var m: Double { self }
var dm: Double { self / 10.0 }
var cm: Double { self / 100.0 }
var mm: Double { self / 1000.0 }
}
var km: Double { self * 1000.0 }
var m: Double { self }
var dm: Double { self / 10.0 }
var cm: Double { self / 100.0 }
var mm: Double { self / 1000.0 }
}
下标
示例
extension Array {
subscript(nullable idx: Int) -> Element? {
if (startIndex..<endIndex).contains(idx) {
return self[idx]
}
return nil
}
}
subscript(nullable idx: Int) -> Element? {
if (startIndex..<endIndex).contains(idx) {
return self[idx]
}
return nil
}
}
方法
示例
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self { task() }
}
mutating func square() -> Int {
self = self * self
return self
}
enum Kind { case negative, zero, positive }
var kind: Kind {
switch self {
case 0: return .zero
case let x where x > 0: return .positive
default: return .negative
}
}
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex { decimalBase *= 10 }
return (self / decimalBase) % 10
}
}
func repetitions(task: () -> Void) {
for _ in 0..<self { task() }
}
mutating func square() -> Int {
self = self * self
return self
}
enum Kind { case negative, zero, positive }
var kind: Kind {
switch self {
case 0: return .zero
case let x where x > 0: return .positive
default: return .negative
}
}
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex { decimalBase *= 10 }
return (self / decimalBase) % 10
}
}
嵌套类型
协议
示例
class Person {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
extension Person : Equatable {
static func == (left: Person, right: Person) -> Bool {
left.age == right.age && left.name == right.name
}
convenience init() {
self.init(age: 0, name: "")
}
}
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
extension Person : Equatable {
static func == (left: Person, right: Person) -> Bool {
left.age == right.age && left.name == right.name
}
convenience init() {
self.init(age: 0, name: "")
}
}
扩展可以给协议提供默认实现,也可以简介实现“可选协议”的效果
扩展可以给协议扩充“协议中从未声明过的方法”
示例
特点:协议扩展独有方法 > 实例方法
协议扩展独有方法 > 实例方法 > 协议和协议扩展都有的方法 > 父类的方法
protocol TestProtocol {
func test1()
}
extension TestProtocol {
func test1() {
print("TestProtocol test1")
}
func test2() {
print("TestProtocol test2") }
}
class TestClass : TestProtocol {
func test1() { print("TestClass test1") }
func test2() { print("TestClass test2") }
}
var cls = TestClass()
cls.test1() // 打印 TestClass test1
cls.test2() // 打印 TestClass test2
var cls2: TestProtocol = TestClass()
cls2.test1() // 打印 TestClass test1
cls2.test2() // 打印 TestProtocol test2 extension 协议的扩展的特殊性,为什么会这样❓
运行结果:
TestClass test1
TestClass test2
TestClass test1
TestProtocol test2 extension
协议扩展独有方法 > 实例方法 > 协议和协议扩展都有的方法 > 父类的方法
protocol TestProtocol {
func test1()
}
extension TestProtocol {
func test1() {
print("TestProtocol test1")
}
func test2() {
print("TestProtocol test2") }
}
class TestClass : TestProtocol {
func test1() { print("TestClass test1") }
func test2() { print("TestClass test2") }
}
var cls = TestClass()
cls.test1() // 打印 TestClass test1
cls.test2() // 打印 TestClass test2
var cls2: TestProtocol = TestClass()
cls2.test1() // 打印 TestClass test1
cls2.test2() // 打印 TestProtocol test2 extension 协议的扩展的特殊性,为什么会这样❓
运行结果:
TestClass test1
TestClass test2
TestClass test1
TestProtocol test2 extension
初始化器
示例
泛型
示例
扩展不能办到的事情
不能覆盖原有功能
不能添加存储属性,不能向已有的属性添加属性观察器
不能添加父类
不能添加指定初始化器
不能添加反初始化器
required初始化器也不能写在扩展中
如果希望自定义初始化器的同时,编译器也能够生成默认初始化器
可以在扩展中编写初始化器
应用
如果一个类型已经实现了协议的所有要求,但是还没有声明它遵守了这个协议,可以通过扩展来遵守并实现某个协议
protocol TestProtocol {
func test()
}
class TestClass {
func test() {
print("test")
}
}
extension TestClass : TestProtocol {}
func test()
}
class TestClass {
func test() {
print("test")
}
}
extension TestClass : TestProtocol {}
示例
编写一个函数,判断一个整数是否是奇数?
func isOdd<T: BinaryInteger>(_ i: T) -> Bool {
i % 2 != 0
}
extension BinaryInteger {
func isOdd() -> Bool { self % 2 != 0 }
}
i % 2 != 0
}
extension BinaryInteger {
func isOdd() -> Bool { self % 2 != 0 }
}
20、访问控制(Access Control)
实例属性的五个访问级别摘要
private: 只能在定义属性的类或结构体中访问
fileprivate: 只能在定义属性的源文件中访问
internal: 只能在同一个模块内的类、结构体和枚举中访问,隐式为internal访问级别
public: 在任何地方都可以访问
open: 在任何地方都可以访问,并且可以被子类重写
fileprivate: 只能在定义属性的源文件中访问
internal: 只能在同一个模块内的类、结构体和枚举中访问,隐式为internal访问级别
public: 在任何地方都可以访问
open: 在任何地方都可以访问,并且可以被子类重写
访问控制的传递性
类:所有成员均遵循最大可见性规则
属性
实例属性
遵循最大访问级别规则
最大可见性规则
类属性
遵循最大访问级别规则
最大可见性规则
静态属性
遵循最大访问级别规则
最大可见性规则
方法
实例方法
遵循最大访问级别规则
最大可见性规则
类方法
遵循最大访问级别规则
最大可见性规则
静态方法
遵循最大访问级别规则
最大可见性规则
结构体
所有成员均遵循最大可见性规则
枚举
所有成员均遵循最大可见性规则
扩展
隐式遵循所属类型的的可见性规则,所有成员均遵循最大可见性规则
规则
不能给用于遵守协议的扩展显式设置扩展的访问级别
在同一文件中的扩展,可以写成类似多个部分的类型声明
在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它
其他先忽略
访问控制的使用准则
一个实体不可以被更低访问级别的实体定义,比如
变量、常量类型>=变量常量
参数类型、返回值类型>=函数
父类>=子类
父协议>=子类
原类型>=typealias
原始值类型、关联值类型>=枚举类型
定义类型A时用到的其他类型>=类型A
元组
元组的访问级别为所有成员类型中级别最低的那个成员类型的访问级别
示例
泛型类型
泛型类型的访问级别是类型的访问级别以及所有泛型类型参数的访问级别中最低的那个泛型类型或者参数的访问级别
成员、嵌套类型
类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别
一般情况下,类型为private或fileprivate,那么成员、嵌套类型默认也是private或者fileprivate
一般情况下,类型为internal或者public,那么成员、嵌套类型默认是internal
示例
成员的重写
子类重写成员的访问级别必须>=子类的访问级别,或者>=父类被重写成员的访问级别
父类的成员不能被成员作用域外定义的子类重写
图示
在全局作用域下定义的private等价于fileprivate
getter、setter
默认自动接收他们所属环境的访问级别
可以给setter单独设置一个比getter更低的访问级别,用一限制写的权限
示例
初始化器
如果一个public类想在另外一个模块调用编译生成的默认无参初始化器,必须显示提供public的无参初始化器
因为public类的默认初始化器是internal级别
required初始化器》=它的默认访问级别
如果结构体有private、fileprivate的存储实例属性,那么它的成员初始化器也是private、fileprivate,否在默认是internal
枚举类型的case
不能给enum的每个case单独设置访问级别
每个case自动接收enum的访问级别
public enum定义的case也是public
协议
协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别
public协议定义的要求是public
协议实现的访问级别必须》=类型的访问级别,或者》=协议的访问级别
21、内存管理
跟oc一样,针对堆空间,swift也采取基于引用计数的ARC内存管理方案
swift的arc中有三种引用
强引用(strong reference)
默认情况下,引用都是强引用
弱引用(weak reference)
通过weak定义弱引用
必须是可选类型的var,因为实例销毁后,arc会自动将弱引用设置为nil
arc自动给弱引用设置nil时,不会触发属性观察器
说明手动置空的时候会触发属性观察器
无主引用(unowned reference)
通过unowned定义无主引用
规则
不会产生强引用,实例销毁后仍然存储着实例的内存地址,类似oc中的unsafe_unretained
视图在实例销毁后访问无主引用,会产生运行时错误(野指针)
fatal error:attempted to read an unowned reference but object 0x0 was already deallocated
规则
weak、unowned
只能用在类实例上面,结构体和枚举不可以用
示例
weak、 unowned 只能用在类实例上面
protocol Livable: AnyObject {}
class Person{}
weak var p0: Person?
weak var p1: AnyObject?
weak var p2: Livable?
unowned var p10: Person?
unowned var p11: AnyObject?
unowned var p12: Livable?
protocol Livable: AnyObject {}
class Person{}
weak var p0: Person?
weak var p1: AnyObject?
weak var p2: Livable?
unowned var p10: Person?
unowned var p11: AnyObject?
unowned var p12: Livable?
Autoreleasepool
public func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result
autoreleasepool
{
let p = MJPerson(age: 20, name: "Jack")
p.run()
}
autoreleasepool
{
let p = MJPerson(age: 20, name: "Jack")
p.run()
}
逃逸闭包 @escaping
背景
非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数
区分
非逃逸闭包
闭包调用发生在函数结束前,闭包调用在函数作用域内
逃逸闭包
闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过@escaping声明
示例
import Dispatch
typealias Fn = () -> ()
class Person {
var fn: Fn
// fn是逃逸闭包
init(fn: @escaping Fn) {
self.fn = fn }
func run() {
// DispatchQueue.global().async也是一个逃逸闭包
// 它用到了实例成员(属性、方法),编译器会强制要求明确写出self DispatchQueue.global().async {
self.fn() }
} }
// fn是非逃逸闭包
func test1(_ fn: Fn) { fn() }
// fn是逃逸闭包
var gFn: Fn?
func test2(_ fn: @escaping Fn) { gFn = fn }
// fn是逃逸闭包
func test3(_ fn: @escaping Fn) {
DispatchQueue.global().async { fn()
} }
typealias Fn = () -> ()
class Person {
var fn: Fn
// fn是逃逸闭包
init(fn: @escaping Fn) {
self.fn = fn }
func run() {
// DispatchQueue.global().async也是一个逃逸闭包
// 它用到了实例成员(属性、方法),编译器会强制要求明确写出self DispatchQueue.global().async {
self.fn() }
} }
// fn是非逃逸闭包
func test1(_ fn: Fn) { fn() }
// fn是逃逸闭包
var gFn: Fn?
func test2(_ fn: @escaping Fn) { gFn = fn }
// fn是逃逸闭包
func test3(_ fn: @escaping Fn) {
DispatchQueue.global().async { fn()
} }
注意点
逃逸闭包
不可以捕获inout参数
示例
typealias Fn = () -> ()
func other1(_ fn: Fn) { fn() }
func other2(_ fn: @escaping Fn) { fn() }
func test(value: inout Int) -> Fn {
other1 { value += 1 }
// error: 逃逸闭包不能捕获inout参数
other2 { value += 1 }
func plus() { value += 1 }
// error: 逃逸闭包不能捕获inout参数 return plus
}
func other1(_ fn: Fn) { fn() }
func other2(_ fn: @escaping Fn) { fn() }
func test(value: inout Int) -> Fn {
other1 { value += 1 }
// error: 逃逸闭包不能捕获inout参数
other2 { value += 1 }
func plus() { value += 1 }
// error: 逃逸闭包不能捕获inout参数 return plus
}
循环引用(Reference Cycle)
如何解决
weak、unowned
使用weak、unowned都能解决循环引用的问题,unowned要比weak小一些性能消耗
两个都可以解决循环引用,何时使用weak,何时使用unowned呢?
在生命周期中可能会变为空的使用weak
初始化复制后再也不会变为空的使用unowned
常见的循环引用
闭包的循环引用
闭包表达式默认会对用到的外层对象产生额外的强引用,对外层对象进行了retain操作
产生循环引用的示例
class Person {
var fn: (() -> ())?
func run() { print("run") }
deinit{ print("deinit") }
}
func test() {
let p = Person()
p.fn= { p.run() }
}
test()
//在闭包表达式的捕获列表声明 weak 或unowned 引用,解决循环引用问题
p.fn = {[unowned p] in p.run()}
p.fn = {
[weak p] in p?.run()
}
p.fn = {
[weak wp = p, unowned up = p, a = 10+20] in
wp?.run()
}
var fn: (() -> ())?
func run() { print("run") }
deinit{ print("deinit") }
}
func test() {
let p = Person()
p.fn= { p.run() }
}
test()
//在闭包表达式的捕获列表声明 weak 或unowned 引用,解决循环引用问题
p.fn = {[unowned p] in p.run()}
p.fn = {
[weak p] in p?.run()
}
p.fn = {
[weak wp = p, unowned up = p, a = 10+20] in
wp?.run()
}
如何解决
在闭包表达式的捕获列表声明 weak 或unowned 引用,解决循环引用问题
//示例
p.fn = {[unowned p] in p.run()}
p.fn = {
[weak p] in p?.run()
}
p.fn = {
[weak wp = p, unowned up = p, a = 10+20] in
wp?.run()
}
p.fn = {[unowned p] in p.run()}
p.fn = {
[weak p] in p?.run()
}
p.fn = {
[weak wp = p, unowned up = p, a = 10+20] in
wp?.run()
}
如果想在定义闭包属性的同时引用self,这个闭包必须是lazy的
为什么呢
因为在实例初始化完毕之后才能引用self
左边的闭包fn内部如果用到了实例成员(属性或者方法),编译器会强制要求明确写明self
// 示例
class Person
{
lazy var fn: (() -> ()) = {
[weak self] in self?. run()
}
func run() { print("run") }
deinit { print("deinit") }
}
class Person
{
lazy var fn: (() -> ()) = {
[weak self] in self?. run()
}
func run() { print("run") }
deinit { print("deinit") }
}
如果 lazy 属性是闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生周期就结束了)
class Person
{
var age: Int = 0
lazy var getAge: Int = {self.age}()
deinit{ print("deinit") }
}
class Person
{
var age: Int = 0
lazy var getAge: Int = {self.age}()
deinit{ print("deinit") }
}
{
var age: Int = 0
lazy var getAge: Int = {self.age}()
deinit{ print("deinit") }
}
class Person
{
var age: Int = 0
lazy var getAge: Int = {self.age}()
deinit{ print("deinit") }
}
如上两个示例对比,一个是闭包属性,一个是以闭包的执行结果作为属性
指针
背景
swift也有专门的指针类型,这些都被定性为“Unsafe”(不安全),常见的有以下4中类型
UnsafePointer<Pointee>类似于const Pointee*
UnsafeMutablePointer<Pointee>类似于Pointee*
UnsafeRawPointer类似于const void*
UnsafeMutableRawPointer类似于 void *
示例
示例1
应用
(遍历替代break)示例2
示例
获得指向某个变量的指针
示例
获得指向堆空间实例的指针
示例
创建指针
示例
创建
存
直接存
移位存
增强指针存
取
直接取
移位如何取
增强指针如何取
销毁
普通的如何销毁
增强的如何销毁
示例2
后续再做了解
示例3
指针之间的转换
示例
unsafeBitCast是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据
类似于C++中的reinterpret_cast
内存访问冲突(Conflicting Access to Memory)
内存访问冲突会在两个访问满足下列条件时发生:
至少一个是写入操作
它们访问的是同一块内存
它们的访问时间重叠(比如在同一个函数内)
如果下面的条件可以满足,就说明重叠访问结构体的属性时安全的
你只访问实例存储属性,不是计算属性或者类属性
结构体是局部变量而非全局变量
结构体要么没有被闭包捕获要么只被非逃逸闭包捕获
示例
// 示例一
// 不存在内存访问冲突
func plus(_ num: inout Int) -> Int { num + 1 } var number = 1
number = plus(&number)
// 存在内存访问冲突
// Simultaneous accesses to 0x0, but modification requires exclusive access var step = 1
func increment(_ num: inout Int) { num += step }
increment(&step)
// 解决内存访问冲突
var copyOfStep = step increment(©OfStep) step = copyOfStep
// 示例二
func balance(_ x: inout Int, _ y: inout Int) { let sum = x + y
x = sum / 2
y = sum - x }
var num1 = 42
var num2 = 30
balance(&num1, &num2) // OK balance(&num1, &num1) // Error
struct Player {
var name: String
var health: Int
var energy: Int
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health) }
}
var oscar = Player(name: "Oscar", health: 10, energy: 10) var maria = Player(name: "Maria", health: 5, energy: 10) oscar.shareHealth(with: &maria) // OK oscar.shareHealth(with: &oscar) // Error
var tulpe = (health: 10, energy: 20) // Error
balance(&tulpe.health, &tulpe.energy)
var holly = Player(name: "Holly", health: 10, energy: 10) // Error
balance(&holly.health, &holly.energy)
// 不存在内存访问冲突
func plus(_ num: inout Int) -> Int { num + 1 } var number = 1
number = plus(&number)
// 存在内存访问冲突
// Simultaneous accesses to 0x0, but modification requires exclusive access var step = 1
func increment(_ num: inout Int) { num += step }
increment(&step)
// 解决内存访问冲突
var copyOfStep = step increment(©OfStep) step = copyOfStep
// 示例二
func balance(_ x: inout Int, _ y: inout Int) { let sum = x + y
x = sum / 2
y = sum - x }
var num1 = 42
var num2 = 30
balance(&num1, &num2) // OK balance(&num1, &num1) // Error
struct Player {
var name: String
var health: Int
var energy: Int
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health) }
}
var oscar = Player(name: "Oscar", health: 10, energy: 10) var maria = Player(name: "Maria", health: 5, energy: 10) oscar.shareHealth(with: &maria) // OK oscar.shareHealth(with: &oscar) // Error
var tulpe = (health: 10, energy: 20) // Error
balance(&tulpe.health, &tulpe.energy)
var holly = Player(name: "Holly", health: 10, energy: 10) // Error
balance(&holly.health, &holly.energy)
22、字面量(Literal)
什么是字面量
示例
常见字面量的默认类型
ppublic typealias IntegerLiteralType = Int
public typealias FloatLiteralType = Double
public typealias BooleanLiteralType = Bool
public typealias StringLiteralType = String
public typealias FloatLiteralType = Double
public typealias BooleanLiteralType = Bool
public typealias StringLiteralType = String
背景
字面量初始化
swift自带的绝大部分类型,都支持直接通过字面量进行初始化
Bool、Int、Float、Double、String、Array、Dictionary、Set、Optional等
swift自带类型为什么可以通过字面量初始化?
因为它们遵守了对应的协议
Bool : ExpressibleByBooleanLiteral
Int : ExpressibleByIntegerLiteral
Float、Double : ExpressibleByIntegerLiteral、ExpressibleByFloatLiteral p Dictionary : ExpressibleByDictionaryLiteral
String : ExpressibleByStringLiteral
Array、Set : ExpressibleByArrayLiteral p Optional : ExpressibleByNilLiteral
Int : ExpressibleByIntegerLiteral
Float、Double : ExpressibleByIntegerLiteral、ExpressibleByFloatLiteral p Dictionary : ExpressibleByDictionaryLiteral
String : ExpressibleByStringLiteral
Array、Set : ExpressibleByArrayLiteral p Optional : ExpressibleByNilLiteral
示例
字面量协议
最佳实践
示例
// 示例1: 值类型(Int)和引用类型(自定义类Student)字面量协议的实践
extension Int : ExpressibleByBooleanLiteral {
public init(booleanLiteral value: Bool) { self = value ? 1 : 0 }
}
var num: Int = true
print(num) // 1
n 有点类似于C++中的转换构造函数
class Student : ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByStringLiteral, CustomStringConvertible {
var name: String = ""
var score: Double = 0
required init(floatLiteral value: Double) { self.score = value }
required init(integerLiteral value: Int) { self.score = Double(value) }
required init(stringLiteral value: String) { self.name = value }
required init(unicodeScalarLiteral value: String) { self.name = value }
required init(extendedGraphemeClusterLiteral value: String) { self.name = value } var description: String { "name=\(name),score=\(score)" }
}
var stu: Student = 90
print(stu) // name=,score=90.0 stu = 98.5
print(stu) // name=,score=98.5 stu = "Jack"
print(stu) // name=Jack,score=0.0
// 示例2:数组字面量协议、字典字面量协议的最佳实践
struct Point {
var x = 0.0, y = 0.0
}
extension Point : ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral {
init(arrayLiteral elements: Double...) { guard elements.count > 0 else { return } self.x = elements[0]
guard elements.count > 1 else { return } self.y = elements[1]
}
init(dictionaryLiteral elements: (String, Double)...) {
for (k, v) in elements {
if k == "x" { self.x = v } else if k == "y" { self.y = v }
} }
}
var p: Point = [10.5, 20.5] print(p) // Point(x: 10.5, y: 20.5) p = ["x" : 11, "y" : 22]
print(p) // Point(x: 11.0, y: 22.0)
extension Int : ExpressibleByBooleanLiteral {
public init(booleanLiteral value: Bool) { self = value ? 1 : 0 }
}
var num: Int = true
print(num) // 1
n 有点类似于C++中的转换构造函数
class Student : ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByStringLiteral, CustomStringConvertible {
var name: String = ""
var score: Double = 0
required init(floatLiteral value: Double) { self.score = value }
required init(integerLiteral value: Int) { self.score = Double(value) }
required init(stringLiteral value: String) { self.name = value }
required init(unicodeScalarLiteral value: String) { self.name = value }
required init(extendedGraphemeClusterLiteral value: String) { self.name = value } var description: String { "name=\(name),score=\(score)" }
}
var stu: Student = 90
print(stu) // name=,score=90.0 stu = 98.5
print(stu) // name=,score=98.5 stu = "Jack"
print(stu) // name=Jack,score=0.0
// 示例2:数组字面量协议、字典字面量协议的最佳实践
struct Point {
var x = 0.0, y = 0.0
}
extension Point : ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral {
init(arrayLiteral elements: Double...) { guard elements.count > 0 else { return } self.x = elements[0]
guard elements.count > 1 else { return } self.y = elements[1]
}
init(dictionaryLiteral elements: (String, Double)...) {
for (k, v) in elements {
if k == "x" { self.x = v } else if k == "y" { self.y = v }
} }
}
var p: Point = [10.5, 20.5] print(p) // Point(x: 10.5, y: 20.5) p = ["x" : 11, "y" : 22]
print(p) // Point(x: 11.0, y: 22.0)
23、匹配模式
什么是模式
模式是用于匹配的规则,比如switch的case、捕捉错误的catch、if、guard、while、for语句的条件等
swift中的模式有
通配符模式(Wildcard Pattern)
分类
_ 匹配任何值
_? 匹配非nil值
最佳实践
通配符_来表示,如:case .human(let name, _):
详见:
enum Life {
case human(name: String, age: Int?)
case animal(name: String, age: Int?)
}
func check(_ life: Life) {
switch life {
case .human(let name, _):
print("human", name)
case .animal(let name, _?):
print("animal", name)
default:
print("other")
}
}
check(.human(name: "Rose", age: 20)) // human Rose
check(.human(name: "Jack", age: nil)) // human Jack
check(.animal(name: "Dog", age: 5)) // animal Dog
check(.animal(name: "Cat", age: nil)) // other
详见:
enum Life {
case human(name: String, age: Int?)
case animal(name: String, age: Int?)
}
func check(_ life: Life) {
switch life {
case .human(let name, _):
print("human", name)
case .animal(let name, _?):
print("animal", name)
default:
print("other")
}
}
check(.human(name: "Rose", age: 20)) // human Rose
check(.human(name: "Jack", age: nil)) // human Jack
check(.animal(name: "Dog", age: 5)) // animal Dog
check(.animal(name: "Cat", age: nil)) // other
标识符模式(Identifier Pattern)
示例
var age = 10
let name = "jack"
let name = "jack"
背景
给对应的变量、常量名赋值
值绑定模式(Value-Binding Pattern)
示例
let point = (3, 2)
switch point {
case let (x, y):
print("The point is at (\(x), \(y)).")
}
switch point {
case let (x, y):
print("The point is at (\(x), \(y)).")
}
元组模式(Tuple Pattern)
示例
let points = [(0, 0), (1, 0), (2, 0)]
for (x, _) in points {
print(x)
}
let name: String? = "jack"
let age = 18
let info: Any = [1, 2]
switch (name, age, info) {
case (_?, _ , _ as String): print("case")
default: print("default")
} // default
var scores = ["jack" : 98, "rose" : 100, "kate" : 86]
for (name, score) in scores {
print(name, score)
}
for (x, _) in points {
print(x)
}
let name: String? = "jack"
let age = 18
let info: Any = [1, 2]
switch (name, age, info) {
case (_?, _ , _ as String): print("case")
default: print("default")
} // default
var scores = ["jack" : 98, "rose" : 100, "kate" : 86]
for (name, score) in scores {
print(name, score)
}
枚举Case模式(Enumeration Case Pattern)
if case语句等价于只有1个case的switch语句
示例
总结:除了switch case 之外还可以有 if case、guard case 、for case
几乎所有的条件判断语句都可以结合 case 使用
let age = 2
// 原来的写法
if age >= 0 && age <= 9 {
print("[0, 9]")
}
// 枚举Case模式
if case 0...9 = age {
print("[0, 9]")
}
guard case 0...9 = age else { return }
print("[0, 9]")
switch age {
case 0...9: print("[0, 9]")
default: break
}
let ages: [Int?] = [2, 3, nil, 5]
for case nil in ages {
print("有nil值")
break
} // 有nil值
let points = [(1, 0), (2, 1), (3, 0)]
for case let (x, 0) in points {
print(x)
} // 1 3
几乎所有的条件判断语句都可以结合 case 使用
let age = 2
// 原来的写法
if age >= 0 && age <= 9 {
print("[0, 9]")
}
// 枚举Case模式
if case 0...9 = age {
print("[0, 9]")
}
guard case 0...9 = age else { return }
print("[0, 9]")
switch age {
case 0...9: print("[0, 9]")
default: break
}
let ages: [Int?] = [2, 3, nil, 5]
for case nil in ages {
print("有nil值")
break
} // 有nil值
let points = [(1, 0), (2, 1), (3, 0)]
for case let (x, 0) in points {
print(x)
} // 1 3
可选模式(Optional Pattern)
示例
let age: Int? = 42
if case .some(let x) = age { print(x) }
if case let x? = age { print(x) }
func check(_ num: Int?) {
switch num {
case 2?: print("2")
case 4?: print("4")
case 6?: print("6")
case _?: print("other")
case _: print("nil")
}
}
check(4) // 4
check(8) // other
check(nil) // nil
let ages: [Int?] = [nil, 2, 3, nil, 5]
for case let age? in ages {
print(age)
} // 2 3 5
let ages: [Int?] = [nil, 2, 3, nil, 5]
for item in ages {
if let age = item {
print(age)
}
} // 跟上面的for,效果是等价的
if case .some(let x) = age { print(x) }
if case let x? = age { print(x) }
func check(_ num: Int?) {
switch num {
case 2?: print("2")
case 4?: print("4")
case 6?: print("6")
case _?: print("other")
case _: print("nil")
}
}
check(4) // 4
check(8) // other
check(nil) // nil
let ages: [Int?] = [nil, 2, 3, nil, 5]
for case let age? in ages {
print(age)
} // 2 3 5
let ages: [Int?] = [nil, 2, 3, nil, 5]
for item in ages {
if let age = item {
print(age)
}
} // 跟上面的for,效果是等价的
类型转换模式(Type-Casting Pattern)
示例
厉害了,厉害了
let num: Any = 6
switch num {
case is Int:
// 编译器依然认为num是Any类型
print("is Int", num)
//case let n as Int:
// print("as Int", n + 1)
default:
break
}
class Animal { func eat() { print(type(of: self), "eat") } }
class Dog : Animal { func run() { print(type(of: self), "run") } }
class Cat : Animal { func jump() { print(type(of: self), "jump") } }
func check(_ animal: Animal) {
switch animal {
case let dog as Dog:
dog.eat()
dog.run()
case is Cat:
animal.eat()
default: break
}
}
// Dog eat
// Dog run
check(Dog())
// Cat eat
check(Cat())
switch num {
case is Int:
// 编译器依然认为num是Any类型
print("is Int", num)
//case let n as Int:
// print("as Int", n + 1)
default:
break
}
class Animal { func eat() { print(type(of: self), "eat") } }
class Dog : Animal { func run() { print(type(of: self), "run") } }
class Cat : Animal { func jump() { print(type(of: self), "jump") } }
func check(_ animal: Animal) {
switch animal {
case let dog as Dog:
dog.eat()
dog.run()
case is Cat:
animal.eat()
default: break
}
}
// Dog eat
// Dog run
check(Dog())
// Cat eat
check(Cat())
表达式模式(Expression Pattern)
示例
let point = (1, 2)
switch point {
case (0, 0):
print("(0, 0) is at the origin.")
case (-2...2, -2...2):
print("(\(point.0), \(point.1)) is near the origin.")
default:
print("The point is at (\(point.0), \(point.1)).")
} // (1, 2) is near the origin.
switch point {
case (0, 0):
print("(0, 0) is at the origin.")
case (-2...2, -2...2):
print("(\(point.0), \(point.1)) is near the origin.")
default:
print("The point is at (\(point.0), \(point.1)).")
} // (1, 2) is near the origin.
表达式模式用在case中
自定义表达式模式
示例1
厉害了,通过重载student的不等于运算符,switch-case的直接比较
struct Student {
var score = 0, name = ""
static func ~= (pattern: Int, value: Student) -> Bool { value.score >= pattern }
static func ~= (pattern: ClosedRange<Int>, value: Student) -> Bool { pattern.contains(value.score) }
static func ~= (pattern: Range<Int>, value: Student) -> Bool {
pattern.contains(value.score)
}
// static func > (pattern: Int, value: Student) -> Bool { value.score > pattern }
static func < (lhs: Int, rhs: Student) -> Bool {
return (lhs < rhs.score)
}
static func > (lhs: Int, rhs: Student) -> Bool {
return (lhs > rhs.score)
}
}
var stu = Student(score: 75, name: "Jack")
if case 60 = stu {
print(">= 60") // 该行为什么会打印?
} // >= 60
if 60 < stu {
print("60 < stu")
} // >= 60
if 76 > stu {
print("60 > stu")
} // >= 60
switch stu {
case 100: print(">= 100")
case 90: print(">= 90")
case 80..<90: print("[80, 90)")
case 60...79: print("[60, 79]")
case 0: print(">= 0")
default: break
} // [60, 79]
var info = (Student(score: 70, name: "Jack"), "及格")
switch info {
case let (60, text): print(text)
default: break
} // 及格
struct Student {
var score = 0, name = ""
static func ~= (pattern: Int, value: Student) -> Bool { value.score >= pattern }
static func ~= (pattern: ClosedRange<Int>, value: Student) -> Bool { pattern.contains(value.score) }
static func ~= (pattern: Range<Int>, value: Student) -> Bool {
pattern.contains(value.score)
}
// static func > (pattern: Int, value: Student) -> Bool { value.score > pattern }
static func < (lhs: Int, rhs: Student) -> Bool {
return (lhs < rhs.score)
}
static func > (lhs: Int, rhs: Student) -> Bool {
return (lhs > rhs.score)
}
}
var stu = Student(score: 75, name: "Jack")
if case 60 = stu {
print(">= 60") // 该行为什么会打印?
} // >= 60
if 60 < stu {
print("60 < stu")
} // >= 60
if 76 > stu {
print("60 > stu")
} // >= 60
switch stu {
case 100: print(">= 100")
case 90: print(">= 90")
case 80..<90: print("[80, 90)")
case 60...79: print("[60, 79]")
case 0: print(">= 0")
default: break
} // [60, 79]
var info = (Student(score: 70, name: "Jack"), "及格")
switch info {
case let (60, text): print(text)
default: break
} // 及格
示例2
函数作为switch-case 表达式进行匹配
extension String {
static func ~= (pattern: (String) -> Bool, value: String) -> Bool {
pattern(value)
}
}
func hasPrefix(_ prefix: String) -> ((String) -> Bool) { { $0.hasPrefix(prefix) } }
func hasSuffix(_ suffix: String) -> ((String) -> Bool) { { $0.hasSuffix(suffix) } }
var str = "jack"
switch str {
case hasPrefix("j"), hasSuffix("k"):
print("以j开头,以k结尾") default: break
} // 以j开头,以k结尾
extension String {
static func ~= (pattern: (String) -> Bool, value: String) -> Bool {
pattern(value)
}
}
func hasPrefix(_ prefix: String) -> ((String) -> Bool) { { $0.hasPrefix(prefix) } }
func hasSuffix(_ suffix: String) -> ((String) -> Bool) { { $0.hasSuffix(suffix) } }
var str = "jack"
switch str {
case hasPrefix("j"), hasSuffix("k"):
print("以j开头,以k结尾") default: break
} // 以j开头,以k结尾
示例3
运算符重载,函数表达式匹配,运算符匹配
func isEven(_ i: Int) -> Bool { i % 2 == 0 }
func isOdd(_ i: Int) -> Bool { i % 2 != 0 }
extension Int {
static func ~= (pattern: (Int) -> Bool, value: Int) -> Bool {
pattern(value)
}
}
var age = 9
switch age {
case isEven:
print("偶数") case isOdd:
print("奇数") default:
print("其他") }
prefix operator ~>
prefix operator ~>=
prefix operator ~<
prefix operator ~<=
prefix func ~> (_ i: Int) -> ((Int) -> Bool) { { $0 > i } }
prefix func ~>= (_ i: Int) -> ((Int) -> Bool) { { $0 >= i } }
prefix func ~< (_ i: Int) -> ((Int) -> Bool) { { $0 < i } }
prefix func ~<= (_ i: Int) -> ((Int) -> Bool) { { $0 <= i } }
var age2 = 9
switch age2 {
case ~>=0:
print("1")
case ~>10:
print("2")
default: break
} // [0, 10]
func isOdd(_ i: Int) -> Bool { i % 2 != 0 }
extension Int {
static func ~= (pattern: (Int) -> Bool, value: Int) -> Bool {
pattern(value)
}
}
var age = 9
switch age {
case isEven:
print("偶数") case isOdd:
print("奇数") default:
print("其他") }
prefix operator ~>
prefix operator ~>=
prefix operator ~<
prefix operator ~<=
prefix func ~> (_ i: Int) -> ((Int) -> Bool) { { $0 > i } }
prefix func ~>= (_ i: Int) -> ((Int) -> Bool) { { $0 >= i } }
prefix func ~< (_ i: Int) -> ((Int) -> Bool) { { $0 < i } }
prefix func ~<= (_ i: Int) -> ((Int) -> Bool) { { $0 <= i } }
var age2 = 9
switch age2 {
case ~>=0:
print("1")
case ~>10:
print("2")
default: break
} // [0, 10]
可以通过重载运算符,自定义表达式模式的匹配规则
case关键字
where关键字
可以使用where为模式匹配增加匹配条件
示例
// where 结合 switch
var data = (11, "Jack")
switch data {
case let (age, _) where age > 10:
print(data.1, "age>10")
case let (age, _) where age > 0:
print(data.1, "age>0")
default: break
}
// where 结合 associatedType 关联类型,规定其遵循一定的协议
protocol Stackable { associatedtype Element }
protocol Container {
associatedtype Stack : Stackable where Stack.Element : Equatable }
// where 结合 方法,规定其返回值遵循一定的规则
func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable {
return false
}
extension Container where Self.Stack.Element : Hashable { }
var ages = [10, 20, 44, 23, 55]
for age in ages where age > 30 {
print(age)
} // 44 55
var data = (11, "Jack")
switch data {
case let (age, _) where age > 10:
print(data.1, "age>10")
case let (age, _) where age > 0:
print(data.1, "age>0")
default: break
}
// where 结合 associatedType 关联类型,规定其遵循一定的协议
protocol Stackable { associatedtype Element }
protocol Container {
associatedtype Stack : Stackable where Stack.Element : Equatable }
// where 结合 方法,规定其返回值遵循一定的规则
func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable {
return false
}
extension Container where Self.Stack.Element : Hashable { }
var ages = [10, 20, 44, 23, 55]
for age in ages where age > 30 {
print(age)
} // 44 55
24、从oc到swift
编辑器指令
// MARK: 类似于OC中的 #pragma mark
// MARK: - 类似于OC中的 #pragma mark -
// TODO: 用于标记未完成的任务
// FIXME: 用于标记待修复的问题
// MARK: - 类似于OC中的 #pragma mark -
// TODO: 用于标记未完成的任务
// FIXME: 用于标记待修复的问题
条件编译
预编译代码
示例
// 操作系统:macOS\iOS\tvOS\watchOS\Linux\Android\Windows\FreeBSD
#if os(macOS) || os(iOS)
// CPU架构:i386\x86_64\arm\arm64
#elseif arch(x86_64) || arch(arm64)
// swift版本
#elseif swift(<5) && swift(>=3)
// 模拟器
#elseif targetEnvironment(simulator) // 可以导入某模块
#elseif canImport(Foundation)
#else
#endif
#if os(macOS) || os(iOS)
// CPU架构:i386\x86_64\arm\arm64
#elseif arch(x86_64) || arch(arm64)
// swift版本
#elseif swift(<5) && swift(>=3)
// 模拟器
#elseif targetEnvironment(simulator) // 可以导入某模块
#elseif canImport(Foundation)
#else
#endif
预编译宏定义
示例
图示
最佳实践
打印
示例
系统版本检测
示例
API可用性说明
示例
iOS程序的入口
示例
swift调用OC
如何调用(桥接)?
1、新建1个桥接头文件,文件名格式默认为:{targetName}-Bridging-Header.h
2、在{targetName}-Bridging-Header.h 文件中#import OC需要暴露给Swift的内容
#import "MJPerson.h"
Swift调用OC – MJPerson.h int sum(int a, int b);
2、在{targetName}-Bridging-Header.h 文件中#import OC需要暴露给Swift的内容
#import "MJPerson.h"
Swift调用OC – MJPerson.h int sum(int a, int b);
示例代码
MJPerson.h
MJPerson.m
Swift代码
如果C语言暴露给Swift的函数名跟Swift中的其他函数名冲突了,我们如何解决
可以在Swift中使用 @_silgen_name 修改C函数名
OC调用swift
Xcode已经默认生成一个用于OC调用Swift的头文件,文件名格式是: {targetName}-Swift.h
图示
Swift暴露给OC的类最终继承自NSObject n 使用@objc修饰需要暴露给OC的成员
使用@objcMembers修饰类 p代表默认所有成员都会暴露给OC(包括扩展中定义的成员)
最终是否成功暴露,还需要考虑成员自身的访问级别
使用@objcMembers修饰类 p代表默认所有成员都会暴露给OC(包括扩展中定义的成员)
最终是否成功暴露,还需要考虑成员自身的访问级别
示例
Xcode会根据Swift代码生成对应的OC声明,写入{targetName}-Swift.h 文件
示例
示例1
可以通过@objc 重命名Swift暴露给OC的符号名(类名、属性名、函数名等)
示例
选择器(Selector)n
Swift中依然可以使用选择器,使用#selector(name)定义一个选择器,必须是被@objcMembers或@objc修饰的方法才可以定义选择器
示例
String
Swift的字符串类型String,跟OC的NSString,在API设计上还是有较大差异
示例
String的插入和删除
示例
Substring
String可以通过下标、 prefix、 suffix等截取子串,子串类型不是String,而是Substring
示例
Substring和它的base,共享字符串数据
Substring发生修改 或者 转为String时,会分配新的内存存储字符串数据
不知道,居然不知道
String 与 Character
示例
String相关的协议
String相关的协议nBidirectionalCollection 协议包含的部分内容
startIndex 、 endIndex 属性、index 方法
String、Array 都遵守了这个协议
RangeReplaceableCollection 协议包含的部分内容
pappend、insert、remove 方法
String、Array 都遵守了这个协议
Dictionary、Set 也有实现上述协议中声明的一些方法,只是并没有遵守上述协议
多行String
示例
String 与 NSString
String 与 NSString 之间可以随时随地桥接转换
如果你觉得String的API过于复杂难用,可以考虑将String转为NSString
比较字符串内容是否等价
String使用 == 运算符
NSString使用isEqual方法,也可以使用 == 运算符(本质还是调用了isEqual方法)
Swift、OC桥接转换表
String ⇌ NSString
String ← NSMutableString
Array ⇌ NSArray
Array ← NSMutableArray
Dictionary ⇌ NSDictionary
Dictionary ←NSMutableDictionary
Set ⇌NSSet
Set ←NSMutableSet
String ← NSMutableString
Array ⇌ NSArray
Array ← NSMutableArray
Dictionary ⇌ NSDictionary
Dictionary ←NSMutableDictionary
Set ⇌NSSet
Set ←NSMutableSet
只能被class继承的协议 protocol Runnable1: AnyObject {}
被@objc 修饰的协议,还可以暴露给OC去遵守实现
示例
可选协议n可以通过@objc 定义可选协议,这种协议只能被class 遵守
示例
dynamicn
被 @objc dynamic 修饰的内容会具有动态性,比如调用方法会走runtime那一套流程
示例
KVC\KVO
block方式的KVO
示例
Swift 支持 KVC \ KVO 的条件 p属性所在的类、监听器最终继承自 NSObject p用 @objc dynamic 修饰对应的属性
示例
关联对象(Associated Object)
在Swift中,class依然可以使用关联对象
应用
默认情况,extension不可以增加存储属性
借助关联对象,可以实现类似extension为class增加存储属性的效果
示例
资源名管理
示例
这种做法实际上是参考了Android的资源名管理方式
示例
示例2
示例3
资源名管理的其他思路
多线程开发
异步
示例
延迟
示例
异步延迟
示例
多线程开发 – once
dispatch_once在Swift中已被废弃,取而代之 p可以用类型属性或者全局变量\常量
示例
默认自带 lazy + dispatch_once 效果
加锁n gcd信号量
示例
25、函数式编程
Array的常见操作
示例
最佳实践
映射遍历
传入函数
紧凑映射遍历
与映射遍历的区别
排空
过滤
递减计算 reduce
例如累加
强大,强大,功能强大
lazy的优化
示例
optional的map和flatMap
示例
示例2
示例3
函数式编程(Funtional Programming)
函数式编程(Funtional Programming,简称FP)是一种编程范式,也就是如何编写程序的方法论 p 主要思想:把计算过程尽量分解成一系列可复用函数的调用
主要特征:函数是“第一等公民”
函数与其他数据类型一样的地位,可以赋值给其他变量,也可以作为函数参数、函数返回值
函数式编程最早出现在LISP语言,绝大部分的现代编程语言也对函数式编程做了不同程度的支持,比如 p Haskell、JavaScript、Python、Swift、Kotlin、Scala等
函数式编程中几个常用的概念 pHigher-Order Function、Function Currying pFunctor、Applicative Functor、Monad
参考资料
http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html p http://www.mokacoding.com/blog/functor-applicative-monads-in-pictures
主要特征:函数是“第一等公民”
函数与其他数据类型一样的地位,可以赋值给其他变量,也可以作为函数参数、函数返回值
函数式编程最早出现在LISP语言,绝大部分的现代编程语言也对函数式编程做了不同程度的支持,比如 p Haskell、JavaScript、Python、Swift、Kotlin、Scala等
函数式编程中几个常用的概念 pHigher-Order Function、Function Currying pFunctor、Applicative Functor、Monad
参考资料
http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html p http://www.mokacoding.com/blog/functor-applicative-monads-in-pictures
FP时间
FP实践
传统写法
// 传统写法 示例
// 假设要实现以下功能:[(num + 3) * 5 - 1] % 10 / 2 var num = 1
func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
func sub(_ v1: Int, _ v2: Int) -> Int { v1 - v2 }
func multiple(_ v1: Int, _ v2: Int) -> Int { v1 * v2 }
func divide(_ v1: Int, _ v2: Int) -> Int { v1 / v2 }
func mod(_ v1: Int, _ v2: Int) -> Int { v1 % v2 }
divide(mod(sub(multiple(add(num, 3), 5), 1), 10), 2)
// 假设要实现以下功能:[(num + 3) * 5 - 1] % 10 / 2 var num = 1
func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
func sub(_ v1: Int, _ v2: Int) -> Int { v1 - v2 }
func multiple(_ v1: Int, _ v2: Int) -> Int { v1 * v2 }
func divide(_ v1: Int, _ v2: Int) -> Int { v1 / v2 }
func mod(_ v1: Int, _ v2: Int) -> Int { v1 % v2 }
divide(mod(sub(multiple(add(num, 3), 5), 1), 10), 2)
函数式写法
// 示例 嵌套太多 容易晕
func add(_ v: Int) -> (Int) -> Int { { $0 + v } }
func sub(_ v: Int) -> (Int) -> Int { { $0 - v } }
func multiple(_ v: Int) -> (Int) -> Int { { $0 * v } }
func divide(_ v: Int) -> (Int) -> Int { { $0 / v } }
func mod(_ v: Int) -> (Int) -> Int { { $0 % v } }
infix operator >>> : AdditionPrecedence
func >>><A, B, C>(_ f1: @escaping (A) -> B,
_ f2: @escaping (B) -> C) -> (A) -> C {
{
f2(f1($0))
}
}
var fn = add(3) >>> multiple(5) >>> sub(1) >>> mod(10) >>> divide(2)
fn(2)
func add(_ v: Int) -> (Int) -> Int { { $0 + v } }
func sub(_ v: Int) -> (Int) -> Int { { $0 - v } }
func multiple(_ v: Int) -> (Int) -> Int { { $0 * v } }
func divide(_ v: Int) -> (Int) -> Int { { $0 / v } }
func mod(_ v: Int) -> (Int) -> Int { { $0 % v } }
infix operator >>> : AdditionPrecedence
func >>><A, B, C>(_ f1: @escaping (A) -> B,
_ f2: @escaping (B) -> C) -> (A) -> C {
{
f2(f1($0))
}
}
var fn = add(3) >>> multiple(5) >>> sub(1) >>> mod(10) >>> divide(2)
fn(2)
疑问
$0是什么?
第一个参数的通配符
高阶函数(Higher-Order Function)
高阶函数是至少满足下列一个条件的函数
接收一个或者多个函数作为输入(map、fliter、reduce等)
返回一个函数
FP中到处都是高阶函数
柯里化(Currying)
什么是柯里化?
讲一个接收多个参数的函数变换为一系列只接受单个参数的函数
// 柯里化示例
// 柯里化前
func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
add(10, 20)
// 柯里化后:
func add(_ v: Int) -> (Int) -> Int { { $0 + v } }
add(10)(20)
// 柯里化前
func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
add(10, 20)
// 柯里化后:
func add(_ v: Int) -> (Int) -> Int { { $0 + v } }
add(10)(20)
规则
Array、Optional的map方法接收的参数就是一个柯里化函数
示例
有什么作用
柯里化是函数式编程的基础,没有柯里化就没有函数式编程
示例
函数柯里化
运算符柯里化
函子(Functor)
什么是函子
像Array、Optional这样支持map运算的类型,称为函子(Functor)
示例
适用函子(Applicative Functor)
对任意一个函子F,如果能支持一下运算,函子就是一个适用函子
func pure<A>(_ value: A) -> F<A>
func <*><A, B>(fn: F<(A) -> B>, value: F<A>) -> F<B>
func <*><A, B>(fn: F<(A) -> B>, value: F<A>) -> F<B>
不是很理解不是很理解,看视频,看视频
optional可以成为适用函子
示例
Array可以成为适用函子
示例
单子(Monad)
对任意一个类型F,如果能支持以下运算,那么就可以称为是一个单子(Monad)
// 单子示例
func pure<A>(_ value: A) -> F<A>
func flatMap<A, B>(_ value: F<A>, _ fn: (A) -> F<B>) -> F<B>
func pure<A>(_ value: A) -> F<A>
func flatMap<A, B>(_ value: F<A>, _ fn: (A) -> F<B>) -> F<B>
很显然,Array、Optional都是单子
26、面向协议编程
是什么
面向协议编程(Protocol Oriented Programming,简称POP) p是Swift的一种编程范式, Apple于2015年WWDC提出
在Swift的标准库中,能见到大量POP的影子
和OOP的关系?
同时,Swift也是一门面向对象的编程语言(Object Oriented Programming,简称OOP) p 在Swift开发中,OOP和POP是相辅相成的,任何一方并不能取代另一方,POP能弥补OOP一些设计上的不足
什么是OOP
继承的经典使用场合
当多个类(比如A、B、C类)具有很多共性时,可以将这些共性抽取到一个父类中(比如D类),最后A、B、C类继承D类
OOP的三大特性:封装、继承、多态
为什么要采用POP呢
有些问题,使用OOP并不能很好解决
比如 p如何将BVC、DVC 的公共方法run 抽取出来?
基于OOP想到的一些解决方案?
做法:将run方法放到另一个对象A中,然后BVC、DVC拥有对象A属性
缺点:多了一些额外的依赖关系
缺点:多了一些额外的依赖关系
做法:将run方法增加到UIViewController分类中
缺点:UIViewController会越来越臃肿,而且会影响它的其他所有子类
缺点:UIViewController会越来越臃肿,而且会影响它的其他所有子类
做法:将run方法抽取到新的父类,采用多继承?(C++支持多继承)
缺点:会增加程序设计复杂度,产生菱形继承等问题,需要开发者额外解决
缺点:会增加程序设计复杂度,产生菱形继承等问题,需要开发者额外解决
POP的解决方案
示例
示例
POP的注意点
优先考虑创建协议,而不是父类(基类)
优先考虑值类型(struct、enum),而不是引用类型(class) n 巧用协议的扩展功能
不要为了面向协议而使用协议
利用协议实现前缀效果
示例
Base
Base: 类
示例
Base: 协议
示例
利用协议实现类型判断
示例
27、响应式编程(Reactive Programming,简称RP)
p ReactiveX
ü 简称Rx,有众多编程语言的版本,比如RxJava、RxKotlin、RxJS、RxCpp、RxPHP、RxGo、RxSwift等等 ü 官网: http://reactivex.io/
ü github: https://github.com/ReactiveX
p ReactiveX
ü 简称Rx,有众多编程语言的版本,比如RxJava、RxKotlin、RxJS、RxCpp、RxPHP、RxGo、RxSwift等等 ü 官网: http://reactivex.io/
ü github: https://github.com/ReactiveX
什么是RP
也是一种编程范式,于1997年提出,可以简化异步编程,提供更优雅的数据绑定 p一般与函数式融合在一起,所以也会叫做:函数响应式编程(Functional Reactive Programming,简称FRP)
比较著名的、成熟的响应式框架
ReactiveCocoa
简称RAC,有Objective-C、Swift版本
github:https://github.com/ReactiveCocoa
官网: http://reactivecocoa.io/
28、RxSwift
是什么
RxSwift(ReactiveX for Swift),ReactiveX的Swift版本
源码
https://github.com/ReactiveX/RxSwift
中文文档
https://beeth0ven.github.io/RxSwift-Chinese-Documentation/
介绍
RxSwift的github上已经有详细的安装教程,这里只演示CocoaPods方式的安装
1、Podfile
pod install
use_frameworks!
target 'target_name' do pod 'RxSwift', '~> 5' pod 'RxCocoa', '~> 5'
end
use_frameworks!
target 'target_name' do pod 'RxSwift', '~> 5' pod 'RxCocoa', '~> 5'
end
2、命令行
pod repo update
3、导入模块
import RxSwift
import RxCocoa
import RxCocoa
模块说明
RxSwift:Rx标准API的Swift实现,不包括任何iOS相关的内容
RxCocoa:基于RxSwift,给iOS UI控件扩展了很多Rx特性
RxSwift的核心角色
Observable:负责发送事件(Event)
Observer:负责订阅Observable,监听Observable发送的事件(Event)
Event
三种
next:携带具体数据
error:携带错误信息,表明Observable终止,不会再发出事件
completed:表明Observable终止,不会再发出事件
error:携带错误信息,表明Observable终止,不会再发出事件
completed:表明Observable终止,不会再发出事件
示例
图示
使用
创建、订阅Observable1
示例
创建、订阅Observable2
示例
创建Observer
示例
扩展Binder属性
示例
传统的状态监听
有哪些
在开发中经常要对各种状态进行监听,传统的常见监听方案有
KVO
Target-Action
Notification
Delegate pBlock Callback
Target-Action
Notification
Delegate pBlock Callback
缺点
传统方案经常会出现错综复杂的依赖关系、耦合性较高,还需要编写重复的非业务代码
RxSwift的状态监听1
示例
RxSwift的状态监听2
示例
既是Observable,又是Observer
示例
有哪些
诸如UISlider.rx.value、UTextField.rx.text这类属性值,既是Observable,又是Observer 类型
它们是RxCocoa.ControlProperty
Disposable
每当Observable被订阅时,都会返回一个Disposable实例,当调用Disposable的dispose,就相当于取消订阅
在不需要再接收事件时,建议取消订阅,释放资源。有3种常见方式取消订阅
示例
28、标准库源码分析
Swift源码简介
几个可能会经常看的目录
docs:一些文档
stdlib:Swift源码
lib:C++源码
include:C++头文件
stdlib:Swift源码
lib:C++源码
include:C++头文件
Swift于2015年正式开源
github地址: https://github.com/apple/swift
标准库源码位置:https://github.com/apple/swift/tree/master/stdlib/public/core
Array分析
map、filter
https://github.com/apple/swift/blob/master/stdlib/public/core/Sequence.swift
flatMap、compactMap、reduce
https://github.com/apple/swift/blob/master/stdlib/public/core/SequenceAlgorithms.swift
Substring分析
append、lowercased、uppercased
https://github.com/apple/swift/blob/master/stdlib/public/core/Substring.swift
Optional分析
map、flatMap、==、??
https://github.com/apple/swift/blob/master/stdlib/public/core/Optional.swift
Metadata分析
文档: https://github.com/apple/swift/blob/master/docs/ABI/TypeMetadata.rst
其他参考
https://github.com/apple/swift/blob/master/include/swift/ABI/Metadata.h
https://github.com/apple/swift/blob/master/include/swift/ABI/MetadataKind.def
https://github.com/apple/swift/blob/master/include/swift/ABI/MetadataValues.h
https://github.com/apple/swift/blob/master/include/swift/Reflection/Records.h
反射
示例
反射是编程语言中一项强大的能力,比如Java语言的反射机
对于任意一个类型,都能够动态获取这个类的所有属性和方法信息
对于任意一个实例,都能够动态调用它的任意方法和属性
Swift的反射机制目前还比较弱,通过 Mirror 类型来提供简单的反射功能
29、项目实战
常用Swift第三方库
网络请求
https://github.com/Alamofire/Alamofire
图片下载
https://github.com/onevcat/Kingfisher
JSON访问
https://github.com/SwiftyJSON/SwiftyJSON
JSON-Model转换
https://github.com/kakaopensource/KakaJSON
以上库使用的注意点
Kingfisher注意点
Kingfisher默认不支持WebP格式的图片,需要额外安装KingfisherWebP
示例
pod 'KingfisherWebP'
库的导入问题
默认情况下,用到哪个库就要导入哪个库,无疑增加了很多重复的工作量
如何办到全局导入库?
新建一个用于Swift调用OC的桥接文件: targetName-Bridging-Header.h p导入系统库:#import <XX/XX.h> p导入第三方库(Framework形式):#import <XX/XX-Swift.h>
30、专项
内存管理
// 1
在Swift中,内存管理是通过引用计数(reference counting)来实现的。Swift使用自动引用计数(ARC)来跟踪和管理应用程序的内存使用。当你创建一个类实例时,Swift会自动分配内存来存储该实例,并且会在不再需要该实例时释放这些内存。
ARC会在每个类实例被创建时维护一个引用计数。当你将一个新的常量或变量设置为对该实例的引用,引用计数就会增加。相反,当你将对该实例的引用赋值给另一个变量、常量或者当它离开其当前范围时,引用计数就会减少。当引用计数变为零时,ARC会自动释放该实例所占用的内存。
但是,由于循环引用的问题,ARC可能会导致内存泄漏。循环引用发生在两个或多个对象相互保持对彼此的强引用时,这意味着它们互相阻止彼此被释放。为了解决这个问题,Swift提供了弱引用(weak reference)和无主引用(unowned reference)的概念,使得在引用对象之间建立一种“非占有关系”,从而打破循环引用。
- 弱引用:不会增加对象的引用计数,并且当引用的对象被释放时,弱引用会自动变为nil。
- 无主引用:也不会增加对象的引用计数,但是它假定引用的对象在整个生命周期中都存在,并且不会被释放。
通过合理使用弱引用和无主引用,可以避免循环引用导致的内存泄漏问题。
//2
Swift的内存管理基于自动引用计数(Automatic Reference Counting,ARC)原理。ARC跟踪并计算你应用程序中的每个实例的引用次数,当引用次数变为0时,自动释放这些实例占用的内存。
下面是ARC的一般工作原理:
1. **引用计数**:当你创建一个新的类实例时,ARC会分配一块内存来存储这个实例,并将引用计数初始化为1。每当有新的变量或常量引用这个实例时,引用计数会加1;当引用被移除时,引用计数会减1。引用计数为0时,ARC会释放该实例所占用的内存。
2. **循环引用**:ARC是智能的,但仍然容易发生循环引用。循环引用会导致内存泄漏,因为对象之间的强引用形成了闭环,导致它们永远不会被释放。为了避免循环引用,Swift提供了弱引用(weak references)和无主引用(unowned references)的概念。弱引用不会增加引用计数,且当对象被释放时会自动变为nil;无主引用假定引用的对象在整个生命周期中都存在,且不会被释放。
3. **自动管理**:Swift的ARC是自动管理的,这意味着你无需手动添加释放代码。当你的应用程序执行时,ARC会自动跟踪和管理对象的引用次数。
4. **性能优化**:ARC会在合适的时机插入释放代码,以确保对象占用的内存能够及时释放,但也尽量减少额外的开销。
总的来说,Swift的内存管理依靠ARC,它是一种自动化的内存管理机制,可以确保你的应用程序在使用内存时保持高效和稳定。
在Swift中,内存管理是通过引用计数(reference counting)来实现的。Swift使用自动引用计数(ARC)来跟踪和管理应用程序的内存使用。当你创建一个类实例时,Swift会自动分配内存来存储该实例,并且会在不再需要该实例时释放这些内存。
ARC会在每个类实例被创建时维护一个引用计数。当你将一个新的常量或变量设置为对该实例的引用,引用计数就会增加。相反,当你将对该实例的引用赋值给另一个变量、常量或者当它离开其当前范围时,引用计数就会减少。当引用计数变为零时,ARC会自动释放该实例所占用的内存。
但是,由于循环引用的问题,ARC可能会导致内存泄漏。循环引用发生在两个或多个对象相互保持对彼此的强引用时,这意味着它们互相阻止彼此被释放。为了解决这个问题,Swift提供了弱引用(weak reference)和无主引用(unowned reference)的概念,使得在引用对象之间建立一种“非占有关系”,从而打破循环引用。
- 弱引用:不会增加对象的引用计数,并且当引用的对象被释放时,弱引用会自动变为nil。
- 无主引用:也不会增加对象的引用计数,但是它假定引用的对象在整个生命周期中都存在,并且不会被释放。
通过合理使用弱引用和无主引用,可以避免循环引用导致的内存泄漏问题。
//2
Swift的内存管理基于自动引用计数(Automatic Reference Counting,ARC)原理。ARC跟踪并计算你应用程序中的每个实例的引用次数,当引用次数变为0时,自动释放这些实例占用的内存。
下面是ARC的一般工作原理:
1. **引用计数**:当你创建一个新的类实例时,ARC会分配一块内存来存储这个实例,并将引用计数初始化为1。每当有新的变量或常量引用这个实例时,引用计数会加1;当引用被移除时,引用计数会减1。引用计数为0时,ARC会释放该实例所占用的内存。
2. **循环引用**:ARC是智能的,但仍然容易发生循环引用。循环引用会导致内存泄漏,因为对象之间的强引用形成了闭环,导致它们永远不会被释放。为了避免循环引用,Swift提供了弱引用(weak references)和无主引用(unowned references)的概念。弱引用不会增加引用计数,且当对象被释放时会自动变为nil;无主引用假定引用的对象在整个生命周期中都存在,且不会被释放。
3. **自动管理**:Swift的ARC是自动管理的,这意味着你无需手动添加释放代码。当你的应用程序执行时,ARC会自动跟踪和管理对象的引用次数。
4. **性能优化**:ARC会在合适的时机插入释放代码,以确保对象占用的内存能够及时释放,但也尽量减少额外的开销。
总的来说,Swift的内存管理依靠ARC,它是一种自动化的内存管理机制,可以确保你的应用程序在使用内存时保持高效和稳定。
特性
运行时语言
其值不需要在编译时期确定
关键字
mutating
@discardableResult
技巧
lldb指令
查看内存结构
frame variable -R
简写
fr v -R
设计模式
单例模式
其他资源
ASCII码表:https://www.ascii-code.com/
大纲
0 条评论
下一页