46 内存管理
跟OC一样,Swift也是采取基于引用计数的ARC内存管理方案(针对堆空间)
-
强引用(strongreference):默认情况下,引用都是强引用
每次强引用使计数 +1,对象内存不会被提前释放。
-
弱引用(weakreference):通过 weak 定义弱引用
必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为nil,ARC自动给弱引用设置nil时,不会触发属性观察器
-
无主引用(unownedreference):通过 unowned 定义无主引用
不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC中的unsafe_unretained)试图在实例销毁后访问无主引用,会产生运行时错误(野指针)
// 内存泄漏
var fn: (()->())? // 声明函数 无参数, 无返回值
func run() {
print("跑起来了")
}
deinit{
print("类销毁了~")
}
override func viewDidLoad() {
super.viewDidLoad()
self.fn = { // self 强引用fn
self.run() // fn 闭包强引用self
}
if let fnFunc = self.fn {
fnFunc()
}
}
// "跑起来了"
// weak self 解决内存泄漏
var fn: (()->())? // 声明函数 无参数, 无返回值
func run() {
print("跑起来了")
}
deinit{
print("类销毁了~")
}
override func viewDidLoad() {
super.viewDidLoad()
self.fn = {[weak self] in // self 强引用fn
self?.run() fn 闭包弱引用 self
}
if let fnFunc = self.fn {
fnFunc()
}
}
// 跑起来了
// 类销毁了~
47 @escaping 非逃逸闭包 & 逃逸闭包
非逃逸闭包: 闭包调用在函数结束前, 函数作用域内调用
逃逸闭包: 闭包执行在函数结束后. 函数作用域外调用
ps: 注意: 逃逸闭包不可以捕获inout参数
47.1 非逃逸闭包
func doWork(closure: () -> Void) {
print("函数开始")
closure() // 闭包在函数返回前被调用
print("函数结束")
}
// 调用
doWork {
print("闭包执行中")
}
// 函数开始
// 闭包执行中
// 函数结束
47.2 逃逸闭包
struct UserInfo {
let name: String
let email: String
let token: String
}
class NetworkManager {
// 真实登录接口:闭包必须是 @escaping,因为网络请求是异步的
func login(username: String,
password: String,
completion: @escaping (Result<UserInfo, Error>) -> Void) {
// 模拟发起网络请求(实际这里是 URLSession.dataTask)
DispatchQueue.global().asyncAfter(deadline: .now() + 1.5) {
// 模拟服务器返回数据
if username == "test@example.com" && password == "123456" {
let user = UserInfo(name: "张三", email: username, token: "abc123xyz")
completion(.success(user))
} else {
let error = NSError(domain: "LoginError", code: 401,
userInfo: [NSLocalizedDescriptionKey: "账号或密码错误"])
completion(.failure(error))
}
}
// 注意:函数在这里就返回了,但上面的闭包要在 1.5 秒后才执行
// 这就是为什么需要 @escaping
print("登录请求已发出,函数返回")
}
}
NetworkManager().login(username: "test@example.com", password: "123456") { result in
print(result)
}
// 登录请求已发出,函数返回
// success(learn_swift1.UserInfo(name: "张三", email: "test@example.com", token: "abc123xyz"))
48 内存访问冲突
内存访问冲突会在两个访问满足下列条件时发生:
- 至少一个是写入操作
- 它们访问的是同一块内存
- 它们的访问时间重叠(比如在同一个函数内)
// 不存在内存访问冲突
func plus(_ num: inout Int) -> Int {
num + 1
}
var number = 1
number = plus(&number) // 正常输入输出参数, 做 + 1 操作
// 存在访问冲突
var step = 1
func increment(_ num: inout Int){
num += step
}
// Simultaneous accesses to 0x0, but modification requires exclusive access
// 同时访问0x0,但修改需要独占访问
increment(&step)
// num += step 同时调用了, step变量的setter方法 和getter方法 访问冲突
// 解决内存访问冲突
var copyOfStep = step
increment(©OfStep) // 修改copyOfStep 变量
step = copyOfStep // 重新赋值step
49 字面量协议
在 Swift 中,字面量 是指直接写在代码中的值,如数字、文本等,无需通过变量或常量计算得到
// 字面量协议
Bool : ExpressibleByBooleanLiteral
Int : ExpressibleByIntegerLiteral
Float、Double : ExpressibleByIntegerLiteral、ExpressibleByFloatLiteral
Dictionary : ExpressibleByDictionaryLiteral
String : ExpressibleByStringLiteral
Array、Set : ExpressibleByArrayLiteral
Optional : ExpressibleByNilLiteral
// 结构体遵守 Int字面量协议 Float字面量协议
struct Money: ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral {
var cents: Int
init(integerLiteral value: Int) {
self.cents = value * 100
}
init(floatLiteral value: Double) {
self.cents = Int(value * 100)
}
}
// 直接用字面量创建
let price: Money = 19.99
let quantity: Money = 5
print(price.cents) // 1999
// 遵守协议, 可以通过 Int Float 赋值的形式直接创建 结构体
50 匹配模式 (可选类型, where)
50.1 通配符模式 (Wildcard Pattern)
-
_ 匹配: 任何值
-
_? 匹配: 非nil值
// 通配符模式(Wildcard Pattern)
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, _?): // 不能为nil
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
// 注意最后返回other, age不能为nil, 所以执行default
50.2 值绑定模式(Value-Binding Pattern)
let point = (3, 2)
switch point {
case let (x, y):
print("The point is at(\(x),\(y)") // 3,2分别绑定 x, y
}
50.3 元祖模式
// 元祖单匹配
let points = [(0, 0), (1, 0), (2, 0)]
for (x, _) in points {
print(x)
}
// 0
// 1
// 2
// swith 三匹配
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(key, value) in scores {
print(key,value)
}
// rose 100
// kate 86
// jack 98
50.4 枚举case模式
let age = 2
// 1 原来的写法
if age >= 0 && age <= 9 {
print("[0, 9]")
}
// 2 枚举Case模式
if case 0...9 = age { // 匹配 age在0到9区间, 同上
print("[0, 9]")
}
// 3 guard
guard case 0...9 = age else {
return
}
print("[0, 9]")
// 4 switch
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)
} // 13
50.5 表达式模式
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
50.6 where条件
// 1 swift 中的 where
var data = (10, "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
}
// 2 for 循环中的 where
var ages = [10, 20, 44, 23, 55]
for age in ages where age > 30 {
print(age)
}
// 44 55
// 3 协议中的where
protocol Stackable {
associatedtype Element // 关联类型
}
protocol Container {
associatedtype Stack: Stackable where Stack.Element : Equatable
}
// 1 协议 Continer 内部的关联类型 Stack 需要遵守 Stackable 协议
// 2 关联类型 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
}
// 扩展中的 where
extension Container where Self.Stack.Element : Hashable { }
46 函数式编程
函数式编程 = 用纯函数组合成数据管道,描述"做什么",而不是"怎么做"
// 假设你有一个班级的成绩单,需要完成以下任务:
// 1. 找出所有及格的成绩(≥60分)
// 2. 给每个及格成绩加5分(平时分奖励)
// 3. 计算奖励后的总分
let scores = [45, 78, 92, 33, 88, 67, 52, 91]
// 传统做法
// 命令式:一步步告诉计算机"怎么做"
var passingScores = [Int]() // 1. 创建空数组
for score in scores { // 2. 遍历每个分数
if score >= 60 { // 3. 判断是否及格
passingScores.append(score) // 4. 及格的加入数组
}
}
var bonusScores = [Int]() // 5. 创建另一个空数组
for score in passingScores { // 6. 遍历及格分数
bonusScores.append(score + 5) // 7. 加5分后加入新数组
}
var total = 0 // 8. 创建总分变量
for score in bonusScores { // 9. 遍历加分后的分数
total = total + score // 10. 累加
}
print(total) // 输出:78+97+93+72+96 = 436
// 实际计算:78(73+5)+97(92+5)+93(88+5)+72(67+5)+96(91+5) = 436
// 函数式编程
// 函数式:描述"要什么",让系统去"怎么做"
let total = scores
.filter { $0 >= 60 } // 1. 筛选及格
.map { $0 + 5 } // 2. 每人加5分
.reduce(0, +) // 3. 求和
print(total) // 436
51 柯里化
柯里化(Currying) 是将一个接受多个参数的函数,转换成一系列每次只接受一个参数的函数链的技术。
柯里化的核心价值就是 复用。
// 函数
func add(a: Int, b: Int) -> Int {
return a + b
}
let result = add(a: 1, b: 2) // 3
// 柯里化
func add(a: Int) -> (Int) -> Int { // 一个参数 返回个闭包
return { b in
return a + b
}
}
// 使用方式1:分步调用
let addWith1 = add(a: 1) // 得到一个函数 (Int) -> Int
let result = addWith1(2) // 3
// 使用方式2:链式调用
let result2 = add(a: 1)(2) // 3
52 面向协议编程
面向协议 = 定义能力(协议)+ 实现能力(类型)+ 统一使用(协议类型)
// 1. 定义协议 - 声明能力
protocol Flyable {
func fly()
}
// 2. 让类型遵循协议
struct Bird: Flyable {
func fly() {
print("鸟在飞")
}
}
struct Plane: Flyable {
func fly() {
print("飞机在飞")
}
}
// 3. 使用协议
let flyingThings: [Flyable] = [Bird(), Plane()]
for thing in flyingThings {
thing.fly() // 鸟在飞 飞机在飞
}
// ❌ 继承的问题:只能继承一个父类
class Bird { func fly() {} }
class Plane { func fly() {} }
// 无法统一处理 Bird 和 Plane
// ✅ 协议的优点:关注能力,不关注类型
protocol Flyable { func fly() }
// Bird 和 Plane 都能飞,都能被统一处理
53 响应式编程
核心思想: 数据流动 + 变化自动传播 = 数据变了,依赖的地方自动更新
常用框架
- Combine (Apple 官方)
- RxSwift (第三方)
- ReactiveSwift (第三方)