
引子
上回说到,韦爵爷在 "身份验证总舵"(AuthProvider)的代码前犯了难 ------Sendable 认证的三条规矩像三座大山,尤其是 "所有成员都是 Sendable 类型" 的要求,让他摸不着头脑。

而陈近南提到的 "actor 类独行侠" 解法,更让他心痒难耐。
在本堂帮规中,各位少侠将学到如下内容:
- 引子
- 📜 第七章:Sendable 认证拆解 ------ 三条规矩如何落地?
- 规矩一:加 "final",不准子类 "乱改规矩"
- 规矩二:内部无 "裸奔" 的可变状态
- 办法 A:把可变状态改成不可变(适合纯查询场景)
- 办法 B:给可变状态找 "靠山"(适合动态更新场景)
- 规矩三:所有成员都是 Sendable 类型
- 🎯 第八章:最优解 "actor 类"------ 单例的 "独行侠" 之路
- actor 类的 "隔离结界" 原理
- actor 类的优势:比 "靠山令" 更灵活
- actor 类的注意点:"await" 不能忘
- ⚠️ 第九章:应急方案 "@unchecked Sendable"------ 万不得已的选择
- 📋 第十章:江湖总结 ------Swift 6 单例的 "生存指南"
*- 优先选择:能不用单例,就不用
-
- 若用单例,先分 "有无可变状态"
- 场景 A:无可变状态(纯工具类)
- 场景 B:有可变状态(需动态更新)
-
- 避坑要点
这日清晨,韦爵爷干脆搬着小板凳守在天地会总舵,非要把这最后两道难关彻底吃透不可。
📜 第七章:Sendable 认证拆解 ------ 三条规矩如何落地?
陈近南端着一壶热茶,先把 AuthProvider 的代码铺在桌上:"小宝,要拿 Sendable 认证,得先把这三条规矩嚼碎了。咱们一条一条来,先看第一条 ------ 类必须加'final'。"
规矩一:加 "final",不准子类 "乱改规矩"
"这'final'就像天地会的'帮规铁律'," 陈近南解释,"加了它,就不准别的类继承 AuthProvider,避免子类偷偷修改内部逻辑 ------ 好比小宝你定下的'丽春院规矩',谁都不能改,才能保证秩序。"

他先给 AuthProvider 加了 final:
swift
// 第一条规矩:加 final,不准继承
final class AuthProvider {
static let shared = AuthProvider()
// 可变令牌库------这是第二条规矩的"拦路虎"
private var memberTokens: [String: Bool] = [:]
func verifyToken(_ token: String) -> Bool {
return memberTokens[token] ?? false
}
private init() {}
}
规矩二:内部无 "裸奔" 的可变状态
"第二条规矩最关键 ------ 内部不能有'没靠山'的可变状态," 陈近南指着memberTokens
,"这令牌库是'var'类型,还没加任何隔离,就像没穿盔甲的小兵,一遇多线程就会出事。要解决它,有两种办法。"

办法 A:把可变状态改成不可变(适合纯查询场景)
若 AuthProvider 只需 "验证令牌",不需要 "新增 / 删除令牌",可把memberTokens
改成let
,再在初始化时传入所有令牌:
swift
final class AuthProvider: Sendable { // 此时能加 Sendable 了!
static let shared = AuthProvider(
memberTokens: ["天地会-杭州-001": true, "天地会-北京-002": true]
)
// 改成不可变的 let,符合第二条规矩
private let memberTokens: [String: Bool]
func verifyToken(_ token: String) -> Bool {
return memberTokens[token] ?? false
}
// 初始化时传入所有令牌,后续不可修改
private init(memberTokens: [String: Bool]) {
self.memberTokens = memberTokens
}
}
"这种办法最安全," 陈近南说,"就像小宝你把账本钉死,谁都改不了,自然不会出乱子。但要是需要动态更新令牌,这招就没用了。"
办法 B:给可变状态找 "靠山"(适合动态更新场景)
若要支持 "新增令牌",就得给memberTokens
加隔离 ------ 比如投靠 MainActor:
swift
final class AuthProvider: Sendable {
static let shared = AuthProvider()
// 给可变状态加 @MainActor 靠山
@MainActor private var memberTokens: [String: Bool] = [:]
// 验证令牌的方法,要切换到 MainActor 访问状态
func verifyToken(_ token: String) async -> Bool {
await MainActor.run {
return memberTokens[token] ?? false
}
}
// 新增令牌的方法,同样要在 MainActor 执行
func addToken(_ token: String) async {
await MainActor.run {
memberTokens[token] = true
}
}
private init() {}
}
韦爵爷皱眉:"这async/await
是啥?怎么多了这么多代码?"

"这是 Swift 的'异步语法'," 陈近南解释,"要访问 MainActor 管理的状态,得用await
'排队等候'------ 就像你想见康熙,得等太监通报,不能直接闯进去。这样虽麻烦,但能保证状态安全。"
规矩三:所有成员都是 Sendable 类型
"第三条规矩常被忽略,但也容易踩坑," 陈近南举例,"比如小宝你给 AuthProvider 加个'令牌生成器'成员,这生成器要是非 Sendable 类型,AuthProvider 照样拿不到认证。"

他指着memberTokens
:"这字典是 [String: Bool] 类型,String 和 Bool 都是 Sendable 类型,所以字典也是 Sendable 类型 ------ 就像组队办事,每个成员都有'朝廷认证',整个队伍自然也有认证。但要是字典里存的是'非 Sendable 类型',比如自定义的TokenInfo
类,那字典就成了'非 Sendable',AuthProvider 也拿不到认证。"
"那自定义类怎么变 Sendable?" 韦爵爷追问。
"跟 AuthProvider 一样,加 final、无裸奔可变状态、成员都是 Sendable," 陈近南写下示例:
swift
// 自定义 TokenInfo 类,符合 Sendable 规矩
final class TokenInfo: Sendable {
let expireTime: Int // Int 是 Sendable
let memberName: String // String 是 Sendable
init(expireTime: Int, memberName: String) {
self.expireTime = expireTime
self.memberName = memberName
}
}
// 此时字典 [String: TokenInfo] 也是 Sendable 类型
final class AuthProvider: Sendable {
static let shared = AuthProvider()
@MainActor private var memberTokens: [String: TokenInfo] = [:]
// ... 其他方法
private init() {}
}
🎯 第八章:最优解 "actor 类"------ 单例的 "独行侠" 之路
"小宝,要是 AuthProvider 需要频繁更新令牌,又不想投靠 MainActor,还有更优的解法 ------actor 类," 陈近南终于讲到了 "独行侠" 招式,"它自带'隔离结界',不用依赖任何全局演员,就能保证状态安全,还天生是 Sendable 类型。"
actor 类的 "隔离结界" 原理
"actor 类就像一位武功高强的独行侠," 陈近南解释,"它的内部状态只能'自己改',外界要访问,必须'排队申请'------ 不管多少线程来调用,都得按顺序来,绝不会出现'同时改状态'的问题。"

他把 AuthProvider 改成 actor 类:
swift
// 把 class 改成 actor,自带隔离结界
actor AuthProvider {
// 全局共享实例------actor 类天生支持 static let
static let shared = AuthProvider()
// 可变令牌库------不用加任何靠山,actor 自动隔离
private var memberTokens: [String: Bool] = [:]
// 验证令牌:外界调用需加 await,排队访问
func verifyToken(_ token: String) -> Bool {
return memberTokens[token] ?? false
}
// 新增令牌:同样自动排队,不会有数据竞态
func addToken(_ token: String) {
memberTokens[token] = true
}
// actor 类的初始化不用 private?
// 答:actor 类默认禁止外部初始化,不用额外加 private!
init() {}
}
actor 类的优势:比 "靠山令" 更灵活
"你看,这代码比投靠 MainActor 简洁多了," 陈近南对比道,"不用加 @MainActor,不用写 MainActor.run,actor 自动搞定隔离。而且它不依赖 UI 线程,后台线程调用也没问题 ------ 就像独行侠不用看任何人脸色,自己就能把事办得妥妥的。"
韦爵爷试着写了调用代码:
swift
// 后台线程验证令牌
Task.detached {
// 调用 actor 方法需加 await,排队访问
let isValid = await AuthProvider.shared.verifyToken("天地会-杭州-001")
print("令牌是否有效:\(isValid)")
}
// 主线程新增令牌
Task {
await AuthProvider.shared.addToken("天地会-广州-003")
}
"就算两个 Task 同时调用,actor 也会让它们排队执行," 陈近南说,"绝不会出现'一个查、一个改'的混乱 ------ 这比 MainActor 更灵活,因为 MainActor 会把所有任务都堆在 UI 线程,而 actor 会在后台自动调度,不耽误 UI 办事。"

actor 类的注意点:"await" 不能忘
"但有一点要注意," 陈近南提醒,"调用 actor 的任何方法都得加await
,哪怕是读操作 ------ 就像见独行侠要先通报,不能直接推门而入。要是忘了加await
,编译器会直接拦着,这点可比'免死牌'安全多了。"
⚠️ 第九章:应急方案 "@unchecked Sendable"------ 万不得已的选择
"小宝,要是老项目里的单例改起来太麻烦,比如有几百行代码依赖它,又不能停服改造,还有最后一招'应急方案'------@unchecked Sendable," 陈近南的语气变得严肃,"这招比'nonisolated (unsafe)'更猛,相当于给单例贴了张'假认证',编译器虽不拦着,但风险全由你承担。"
他写下示例:
swift
// 加 @unchecked Sendable,跳过编译器检查
final class AuthProvider: @unchecked Sendable {
static let shared = AuthProvider()
// 裸奔的可变状态------编译器不拦着,但实际有风险!
private var memberTokens: [String: Bool] = [:]
func verifyToken(_ token: String) -> Bool {
return memberTokens[token] ?? false
}
func addToken(_ token: String) {
memberTokens[token] = true
}
private init() {}
}
"这招就像你伪造朝廷公文," 陈近南警告,"表面上能通行无阻,实则一旦被发现(出现数据竞态),就是杀头大罪(App 崩溃、数据错乱)。只有在'完全确保状态安全'的情况下才能用,比如单例只在主线程使用,且没有任何异步调用。"

他补充道:"而且这只能是'临时过渡方案',就像小宝你用假公文救急后,总得想办法换成真的。等项目有空了,还是得改成 actor 类或 Sendable 类,才能彻底消除风险。"
📋 第十章:江湖总结 ------Swift 6 单例的 "生存指南"
讲完所有解法,陈近南把《Swift 6 单例秘籍》的最后一页撕下来,递给韦小宝:"这是单例的'生存指南',你收好了,以后在 iOS 江湖闯荡,准用得上。"

1. 优先选择:能不用单例,就不用
"最安全的办法永远是'显式依赖注入'," 陈近南强调,"就像小宝你办事光明正大,不搞暗箱操作,自然不会出岔子。只有在'全 App 必须唯一实例'的场景,比如系统工具、网络管理器,才考虑单例。"
2. 若用单例,先分 "有无可变状态"
场景 A:无可变状态(纯工具类)
-
解法:用
final class + static let + Sendable
,简单安全。 -
示例:时间工具、常量配置类。
场景 B:有可变状态(需动态更新)
-
最优解:用
actor类
,自带隔离,天生安全,灵活度高。 -
次优解:用
Sendable类 + MainActor隔离
,适合与 UI 紧密相关的场景。 -
应急解:用
@unchecked Sendable
或nonisolated(unsafe)
,仅限临时过渡。
3. 避坑要点:
-
别忘
final
:Sendable 类必须加 final,防止子类篡改。 -
状态要隔离:可变状态要么改不可变,要么找靠山(MainActor/actor),别裸奔。
-
await
别漏:调用 actor 方法必须加 await,排队访问才安全。 -
应急需谨慎:
@unchecked Sendable
和nonisolated(unsafe)
,能不用就不用。
韦小宝接过 "生存指南",恍然大悟:"原来 Swift 6 不是'禁绝单例',而是让单例'守规矩'------ 就像康熙爷整顿吏治,不是不让官员办事,而是让官员按规矩办事,这样江湖才能太平。"

陈近南点头笑道:"没错!Swift 6 的所有规矩,都是为了'并发安全'------ 让 App 在多线程时代稳如泰山,不再因数据竞态而崩溃。你把这些解法吃透了,以后不管遇到什么单例问题,都能见招拆招,在 iOS 江湖里横着走!"
自此,韦爵爷不仅摸清了 Swift 6 单例的所有门道,还凭着 "灵活应变" 的本事,帮天地会搞定了 App 的并发问题。而那本《Swift 6 单例秘籍》,也成了 iOS 江湖里流传千古的 "武功宝典"。

江湖路远,代码为伴。
愿各位秃头少侠们都能像韦爵爷一样,在 Swift 6 的新江湖里,手握秘籍,见招拆招,写出安全、稳定的好代码!
感谢观赏,青山不改绿水长流,我们下次再会!8-)