韦爵爷闯荡 Swift 6 江湖:单例秘籍新解(中)

引子

上回说到,韦爵爷摸清了 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 类又如何成为单例的'最优解'",且看下篇分解。

相关推荐
茶底世界之下2 天前
时间线效果层,为什么不能只复用一个处理器?
ios·swift
Geek漫游指南2 天前
PRD 写得越来越漂亮,需求怎么越来越糊涂?
后端·敏捷开发
Cc.Y2 天前
Swift深度链接的极简之道
ios·cocoa·swift
传奇开心果编程2 天前
【SwiftUI提高练中学】第9课 安全与隐私
swiftui·移动开发·swift·编程语言理论·开发范式·编程框架·声明式ui
CocoaKier2 天前
苹果商店详情顶部头图已面向所有开发者开放!
ios·apple
茶底世界之下3 天前
动态切换 nearest / linear 时,为什么不能只替换 sampler
ios·swift
应用市场3 天前
把旧 Pixel 变成相册备份中转站(上):Mac 到安卓的照片传输工具设计——流式上传、sha256 校验、adb forward 与 Bonjour
android·macos·adb·kotlin·swift
东坡肘子3 天前
Swift Server,又多了一个 Google -- 肘子的 Swift 周报 #156
人工智能·swiftui·swift
打工仔折腾 AI4 天前
从零写一个CAD 05:以鼠标为中心的滚轮缩放,招法能复用但顺序不能反
开发语言·后端·python·性能优化·计算机外设·swift·ai agent 实战
茶底世界之下4 天前
视频预览切换为何会闪回旧帧:用 generation + mode identity 管住异步回调
ios·swift