
引子
上回说到,韦爵爷摸清了 Swift 6 对单例的 "第一重难关"------ 非隔离的全局可变状态会遭编译器红牌。

陈近南透露,化解此关分两种情况,还藏着 "改写法引新坑" 的门道。
在本篇帮规中,各位少侠将学到如下内容:
- 引子
- 🔧 第三章:第一招 "常量锁"------static let 如何破红牌?
- 🎭 第四章:第二招 "靠山令"------ 投靠 MainActor 保安全
- 方式一:全类投靠 ------ 整个账本归 MainActor 管
- 方式二:局部投靠 ------ 只让实例归 MainActor 管
- ⚠️ 第五章:第三招 "免死牌"------nonisolated (unsafe) 能救急?
- 🤔 第六章:新疑问 ------Sendable 认证到底怎么拿?
韦爵爷急得抓耳挠腮,次日一早就揣着早点奔往天地会总舵,非要陈近南把 "改写法" 的诀窍说个通透。
🔧 第三章:第一招 "常量锁"------static let 如何破红牌?
陈近南接过韦小宝递来的肉包,指着桌上的代码笑道:"小宝你看,上篇那'战力账本'用的是'static var',改成'static let'试试?" 说着便提笔修改:
swift
// 骁骑营战力账本------将 static var 改成 static let
class GamePiece {
// 现在是常量实例,编译器不拦着了?
static let shared = GamePiece()
// 战力值仍为可变状态
var power = 100
private init() {}
}
韦爵爷凑上前一看,果然,之前那道 "非并发安全" 的红牌消失了!他拍着大腿道:"这么简单?改个关键字就行?"
"哪有这么容易," 陈近南摇头,"这招'常量锁',锁的是'实例本身'------ 就像把'战力账本'的封面钉死,别人拿不走账本,但账本里的页码(内部状态)照样能改。编译器之所以暂时放行,是因为'static let'保证了全江湖只有一个账本实例,不会出现'多本账混乱'的问题。但你再给'身份验证总舵'也加这招试试?"
韦爵爷依言修改 AuthProvider 代码:
swift
class AuthProvider {
static let shared = AuthProvider()
private var memberTokens: [String: Bool] = [:] // 可变令牌库
func verifyToken(_ token: String) -> Bool {
return memberTokens[token] ?? false
}
private init() {}
}
刚改完,编译器就弹出一道新红牌,比之前的更刺眼:
"Static property 'shared' is not concurrency-safe because non-'Sendable' type 'AuthProvider' may have shared mutable state"
(翻译:静态属性 "shared" 不安全!因为 "AuthProvider" 是非 Sendable 类型,可能包含共享可变状态)
韦爵爷顿时懵了:"这'Sendable'又是啥?怎么改个关键字,又冒出新罪名?"

"这就是我所说的'新坑'," 陈近南解释道,"Swift 6 不仅管'实例是否唯一',还管'实例是否能安全跨线程'。Sendable 类型,就像'朝廷认证的公文'------ 只有盖了'安全章'的公文,才能在不同衙门(线程)间传递,不怕被篡改。非 Sendable 类型,好比民间的私函,谁都能改,自然不让跨线程传递。"
他顿了顿,继续说道:"你这 AuthProvider 里有'memberTokens'这个可变状态,又没加'Sendable 认证',编译器怕它在多线程间传递时出乱子,自然要拦着。要破这关,得先搞懂'Sendable 认证'的规矩 ------ 不过眼下,咱们先解决'战力账本'的问题,它虽没出红牌,但内部的'power'还是可变的,多线程修改照样会出事。"

🎭 第四章:第二招 "靠山令"------ 投靠 MainActor 保安全
"那'战力账本'的可变战力值,该怎么管?" 韦爵爷追问。
"给它找个'靠山'------MainActor," 陈近南说道,"MainActor 是 iOS 江湖的'皇宫大殿',所有事都得经它审批,绝不允许'多线程乱插手'。给账本加上'MainActor 印记',战力值的修改就只能在'皇宫大殿'里进行,自然不会出现数据竞态。"
他演示了两种 "投靠" 方式:
方式一:全类投靠 ------ 整个账本归 MainActor 管
swift
// 给 GamePiece 加 @MainActor,全类归皇宫大殿管
@MainActor
class GamePiece {
static let shared = GamePiece()
var power = 100 // 战力值修改,必须经 MainActor 审批
private init() {}
// 修改战力值的方法,自动归 MainActor 管
func updatePower(by value: Int) {
power += value
}
}
"这种方式适合'账本常和 UI 打交道'的场景," 陈近南解释,"比如战力值要实时显示在皇宫的'战报屏'(UI 界面)上,归 MainActor 管后,修改战力值和刷新界面能无缝衔接,不会出现'界面显示旧数据'的问题 ------ 就像小宝你在皇宫里办事,直接面见康熙,不用跑断腿啦。"
方式二:局部投靠 ------ 只让实例归 MainActor 管
swift
class GamePiece {
// 只给 shared 实例加 @MainActor,其他方法不受限
@MainActor static let shared = GamePiece()
var power = 100
private init() {}
// 非 UI 相关的方法,可在其他线程执行
func calculateDamage() -> Int {
return power * 2
}
}
韦爵爷皱眉:"这两种方式有啥区别?"
"全类投靠省心,但不灵活;局部投靠灵活,却容易出错," 陈近南举例,"比如你调用'calculateDamage'计算伤害,这招不用和 UI 打交道,局部投靠时能在后台线程执行,不耽误皇宫办事;但要是你忘了'shared'归 MainActor 管,直接在后台线程改'power',编译器照样会拦着 ------ 就像小宝你私自在宫外办皇宫的事,肯定会被九门提督抓包。"

⚠️ 第五章:第三招 "免死牌"------nonisolated (unsafe) 能救急?
"那要是'靠山'不合适呢?" 韦爵爷突然想起一事,"比如'战力账本'要在夜间统计战力,那时皇宫没人值班,总不能让统计小兵等着吧?"
陈近南闻言,从怀里掏出一块黑色令牌:"这是'unsafe 免死牌'------nonisolated (unsafe),能让编译器暂时闭嘴。但这牌风险极大,就像你的蒙汗药,用错了会出人命。"
他写下代码示例:
swift
class GamePiece {
// 给实例加"免死牌",编译器不再检查并发安全
nonisolated(unsafe) static let shared = GamePiece()
var power = 100
private init() {}
}
"用了这牌,编译器就不管'并发安全'了," 陈近南严肃道,"你得自己保证'只有一个线程能修改战力值'------ 就像你拿着免死牌私闯皇宫,得自己确保不被康熙发现。要是夜间统计和白天修改同时进行,数据竞态照样会让 App 闪退,到时候可没人救你。"

韦爵爷缩了缩脖子:"这么危险?那谁会用这招?"
"只有'老江湖救急'时才用," 陈近南解释,"比如老项目改造,暂时没法加'MainActor 靠山',又不能让 App 停摆,就用这牌过渡。但事后必须尽快换成正规解法,不然早晚会出大问题 ------ 就像小宝你用蒙汗药救急后,总得想办法补回窟窿才好。"
🤔 第六章:新疑问 ------Sendable 认证到底怎么拿?
讲完 "靠山令" 和 "免死牌",陈近南话锋一转:"小宝,你还记得那道'非 Sendable'的红牌吗?其实'身份验证总舵'的问题,比'战力账本'更棘手 ------ 它不仅要管内部可变状态,还得拿'Sendable 认证',才能在多线程间安全传递。"
他指着 AuthProvider 的代码:"要拿 Sendable 认证,得守三条规矩:
- 一是类必须加'final',不准子类篡改;
- 二是内部不能有可变状态,除非有'靠山';
- 三是所有成员都得是 Sendable 类型。
你试试按这规矩改改?"
韦爵爷拿起笔,却迟迟不敢下笔:"这第三条'所有成员都是 Sendable 类型'是啥意思?我这'memberTokens'是字典,算不算 Sendable 类型?还有,要是'身份验证总舵'必须有可变状态,又不能投靠 MainActor,该怎么办?"
陈近南笑道:"小宝问得好!这 Sendable 认证的规矩,藏着不少细节 ------ 比如字典、数组这些集合类型,只有'元素是 Sendable 类型'时,它们才是 Sendable 类型。至于'可变状态 + 不能投靠 MainActor'的情况,江湖上还有一招'独行侠'解法,比'靠山令'更稳妥,比'免死牌'更安全。"

韦爵爷眼睛一亮:"啥'独行侠'解法?快给我说说!"
"这招就是'actor 类'," 陈近南故意放慢语速,"它自带'隔离结界',不用投靠 MainActor,也能保证内部状态安全。不过这招的门道更深,得单独拿一篇细说 ------ 咱们下篇就专门拆解'Sendable 认证的规矩'和'actor 类的用法',保准让你把'非 Sendable'的红牌也彻底化解。"

欲知 "Sendable 认证的三条规矩如何落地","actor 类又如何成为单例的'最优解'",且看下篇分解。