六、闭包
- 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数(Lambdas)比较相似。闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。
1、闭包表达式
- 闭包表达式是一种构建内联闭包的方式,它的语法简洁。在保证不丢失它语法清晰明了的同时,闭包表达式提供了几种优化的语法简写形式
{ (parameters) -> return type in
statements
}
//缩写1
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
//缩写2
reversedNames = names.sorted(by: { $0 > $1 } )
2、尾随闭包
- 如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性,尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式
func exec(v1: Int,v2: Int,fn:(Int,Int) -> Int) {
print(fn(v1,v2))
}
exec(v1: 10, v2: 20) {
$0 + $1
}
- 如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要在函数名后边写圆括号
func exec(fn: (Int, Int) -> Int) {
print(fn(1, 2))
}
exec(fn: { $0 + $1 })
exec() { $0 + $1 }
exec { $0 + $1 }
3、值捕获
- 闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
//makeIncrementer 返回类型为 () -> Int
4、闭包是引用类型
5、逃逸闭包
- 当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 打印出“200”
completionHandlers.first?()
print(instance.x)
// 打印出“100”
6、自动闭包
-
@autoclosure会自动将20封装成闭包{ 20 }
-
@autoclosure只支持() -> T格式的参数
-
@autoclosure并非只支持最后1个参数
-
空合并运算符??使用了@autoclosure技术
-
有@autoclosure、无@autoclosure ,构成了函数重载
func getFirstPositive(_ v1: Int,_ v2: Int)->Int { return v1>0 ? v1 : v2 } func getFirstPositive(_ v1: Int,_ v2: @autoclosure ()-> Int)->Int { return v1>0 ? v1 : v2() } print(getFirstPositive(-1, 20))
七、枚举
1、枚举语法
- 使用 enum 关键词来创建枚举并且把它们的整个定义放在一对大括号内
2、使用Switch语句匹配枚举值
3、枚举成员的遍历
-
令枚举遵循 CaseIterable 协议。Swift 会生成一个 allCases 属性,用于表示一个包含枚举所有成员的集合
enum Beverage: CaseIterable { case coffee, tea, juice } let numberOfChoices = Beverage.allCases.count print("\(numberOfChoices) beverages available") // 打印“3 beverages available” for beverage in Beverage.allCases { print(beverage) } // coffee // tea // juice
4、关联值
-
有时候把其他类型的值和成员值一起存储起来会很有用。这额外的信息称为关联值
enum Barcode { case upc(Int, Int, Int, Int) case qrCode(String) }
5、原始值
-
原始值可以是字符串、字符,或者任意整型值或浮点型值。每个原始值在枚举声明中必须是唯一的
enum ASCIIControlCharacter: Character { case tab = "\t" case lineFeed = "\n" case carriageReturn = "\r" }
-
在使用原始值为整数或者字符串类型的枚举时,不需要显式地为每一个枚举成员设置原始值,Swift 将会自动为你赋值。
-
如果第一个枚举成员没有设置原始值,其原始值将为 0
let positionToFind = 11 if let somePlanet = Planet(rawValue: positionToFind) { switch somePlanet { case .earth: print("Mostly harmless") default: print("Not a safe place for humans") } } else { print("There isn't a planet at position \(positionToFind)") } // 打印“There isn't a planet at position 11”
6、递归枚举
-
递归枚举是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上 indirect 来表示该成员可递归。
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
八、类和结构体
1、结构体和类对比
a.两者的共同点:
- 定义属性用于存储值
- 定义方法用于提供功能
- 定义下标操作用于通过下标语法访问它们的值
- 定义构造器用于设置初始值
- 通过扩展以增加默认实现之外的功能
- 遵循协议以提供某种标准功能
b.不同点:
类:
- 继承允许一个类继承另一个类的特征
- 类型转换允许在运行时检查和解释一个类实例的类型
- 析构器允许一个类实例释放任何其所被分配的资源
- 引用计数允许对一个类的多次引用
c.定义的语法
struct SomeStructure {
// 在这里定义结构体
}
class SomeClass {
// 在这里定义类
}
2、结构体和枚举是值类型
-
值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份
-
类似于对文件进行copy、paste操作 ,产生了全新的文件副本。属于深拷贝( deep copy )
3、类是引用类型
-
引用赋值给var. let或者给函数传参,是将内存地址拷贝一份
-
类似于制作一个文件的替身(快捷方式、链接) , 指向的是同一个文件。属于浅拷贝( shallow copy )
九、属性
1、存储属性
a.语法:
一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。存储属性可以是变量存储属性(用关键字 var 定义),也可以是常量存储属性(用关键字 let 定义)。
- 类似于成员变量这个概念
- 存储在实例的内存中
- 结构体、类可以定义存储属性
- 枚举不可以定义存储属性
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 该区间表示整数 0,1,2
rangeOfThreeItems.firstValue = 6
// 该区间现在表示整数 6,7,8
b.延迟存储属性
- 使用lazy可以定义一个延迟存储属性,在第一次用到属性的时候才会进行初始化
- lazy属性必须是var ,不能是let
- let必须在实例的初始化方法完成之前就拥有值
- 如果多条线程同时第一次访问lazy属性
- 无法保证属性只被初始化1次
- 当结构体包含一个延迟存储属性时,只有var才能访问延迟存储属性
- 因为延迟属性初始化时需要改变结构体的内存
2、计算属性
a.语法
计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
- 本质就是方法(函数)
- 不占用实例的内存
- 枚举、结构体、类都可以定义计算属性
b.注意点
- 如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 newValue
c.只读计算属性
-
只有 getter 没有 setter 的计算属性叫只读计算属性。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
-
必须使用 var 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let 关键字只用来声明常量属性,表示初始化后再也无法修改的值。
-
只读计算属性的声明可以去掉 get 关键字和花括号
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// 打印“the volume of fourByFiveByTwo is 40.0”
3、属性观察器
a.什么位置添加属性观察器:
- 自定义的存储属性
- 继承的存储属性
- 继承的计算属性
b.注意点:
-
可以为非lazy的var存储属性设置属性观察器
-
willSet会传递新值,默认叫newValue
-
didSet会传递旧值,默认叫oldValue
-
在初始化器中设置属性值不会触发willSet和didSet
-
在属性定义时设置初始值也不会触发willSet和didSet
class StepCounter { var totalSteps: Int = 0 { willSet(newTotalSteps) { print("将 totalSteps 的值设置为 \(newTotalSteps)") } didSet { if totalSteps > oldValue { print("增加了 \(totalSteps - oldValue) 步") } } } } let stepCounter = StepCounter() stepCounter.totalSteps = 200 // 将 totalSteps 的值设置为 200 // 增加了 200 步 stepCounter.totalSteps = 360 // 将 totalSteps 的值设置为 360 // 增加了 160 步 stepCounter.totalSteps = 896 // 将 totalSteps 的值设置为 896 // 增加了 536 步
4、属性包装器
a.定义
属性包装器在管理属性如何存储和定义属性的代码之间添加了一个分隔层。
@propertyWrapper
struct TwelveOrLess {
private var number: Int
init() { self.number = 0 }
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
5、全局变量和局部变量
全局变量:在函数、方法、闭包或任何类型之外定义的变量
局部变量:在函数、方法或闭包内部定义的变量
6、类型属性
a.实例属性
- 只能通过实例去访问
- 存储实例属性( Stored Instance Property ) : 存储在实例的内存中,每个实例都有1份
- 计算实例属性( Computed Instance Property)
b.实例属性
- 类型属性( Type Property) :只能通过类型去访问
- 存储类型属性( Stored Type Property) : 整个程序运行过程中,就只有1份内存(类似于全局变量)
- 计算类型属性( Computed Type Property )
- 可以通过static定义类型属性
- 如果是类,也可以用关键字class
c.类型属性细节
-
不同于存储实例属性,你必须给存储类型属性设定初始值
-
因为类型没有像实例那样的init初始化器来初始化存储属性
-
存储类型属性默认就是lazy ,会在第一次使用的时候才初始化
-
就算被多个线程同时访问,保证只会初始化一次
-
存储类型属性可以是let
-
枚举类型也可以定义类型属性(存储类型属性、计算类型属性)
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
print(SomeStructure.storedTypeProperty)
// 打印“Some value.”
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 打印“Another value.”
print(SomeEnumeration.computedTypeProperty)
// 打印“6”
print(SomeClass.computedTypeProperty)
// 打印“27”