惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
Recorded Future
Recorded Future
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The GitHub Blog
The GitHub Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google DeepMind News
Google DeepMind News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Microsoft Azure Blog
Microsoft Azure Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
M
MIT News - Artificial intelligence
云风的 BLOG
云风的 BLOG
Y
Y Combinator Blog
N
News | PayPal Newsroom
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Help Net Security
Help Net Security
博客园 - Franky
SecWiki News
SecWiki News
Recent Announcements
Recent Announcements
T
Troy Hunt's Blog
The Register - Security
The Register - Security
The Last Watchdog
The Last Watchdog
Webroot Blog
Webroot Blog
S
Security Affairs
博客园 - 司徒正美
S
Schneier on Security
I
InfoQ
博客园_首页
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
Threat Research - Cisco Blogs
Forbes - Security
Forbes - Security
腾讯CDC
N
Netflix TechBlog - Medium
N
News and Events Feed by Topic
Cloudbric
Cloudbric
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
A
About on SuperTechFans
Engineering at Meta
Engineering at Meta
Recent Commits to openclaw:main
Recent Commits to openclaw:main
B
Blog
V
Vulnerabilities – Threatpost
C
Check Point Blog
Google DeepMind News
Google DeepMind News
Google Online Security Blog
Google Online Security Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
C
Cisco Blogs
Schneier on Security
Schneier on Security
O
OpenAI News
K
Kaspersky official blog

肘子的 Swift 记事本 | Fatbobman's Blog

当每一次写入都有了新价格 -- 肘子的 Swift 周报 #144 当 Linux 成为“空气”:容器、Agent 与不再重要的“桌面之争” -- 肘子的 Swift 周报 #143 两个 SwiftUI 动画 Bug 排查小记 SPI 加入 Apple,Swift 迈向自举 -- 肘子的 Swift 周报 #142 Swift 还让你 Excited 吗? -- 肘子的 Swift 周报 #141 从 Size Class 到可用空间,horizontalSizeClass 还可靠吗? WWDC 26:AI 帮你看完了,然后呢? -- 肘子的 Swift 周报 #140 WWDC 2026 初印象:符合预期,但更务实 -- 肘子的 Swift 周报 #139 Core Data + Observation:从属性级响应到心智解放 稳定 > 新功能 -- 肘子的 Swift 周报 #138 用自定义 Layout 化解 SwiftUI List 的行高与间距跳变 从社区路标到生态基石:Dave Verwer 的新篇章 -- 肘子的 Swift 周报 #137 消失的 WWDC 愿望单 -- 肘子的 Swift 周报 #136 CocoaPods 正在退场,SwiftPM 才刚到第二章 -- 肘子的 Swift 周报 #135 让 AI 从称手到称心 -- 肘子的 Swift 周报 #134 Swift 并发正被更广泛地接纳 -- 肘子的 Swift 周报 #133 从 OpenSwiftUI 到 DanceUI:换个方式 Dive SwiftUI -- 肘子的 Swift 周报 #132 被 Vibe 摧毁的版权壁垒,与开发者的新护城河 -- 肘子的 Swift 周报 #131 苹果的罕见妥协:当高危漏洞遇上“拒升”潮 -- 肘子的 Swift 周报 #130 一墙之隔,不同的时空 -- 肘子的 Swift 周报 #129 我的 App 审核被卡了? -- 肘子的 Swift 周报 #128 CDE:一次让 Core Data 更像现代 Swift 的尝试 50 岁的苹果和 51 岁的我 -- 肘子的 Swift 周报 #127 2026 年,为什么我仍在思考 Core Data Macbook Neo:苹果重回校园的起点 -- 肘子的 Swift 周报 #126 Xcode 26.3 + Claude Agent:模型替换、MCP、Skill 与自适应配置 macOS 录屏软件开发实录:从像素抓取到元数据重现 isolated(any) 与 #isolation:让 Swift 闭包自动继承隔离域 在 tvOS 上活下來:一個非典型播放器的工程實錄 告别“可移植汇编”:我已让 Swift 在 MCU 上运行七年 独立开发者的试炼:Zipic 从 0 到 1 的产品化之路 逃离 Mac App Store:如何从零构建独立应用的分发与售卖体系 为什么父视图无法修改子视图的 @State Partytown 实战:修复 GTM 自定义事件与 dataLayer 失效问题 Swift 6 编译加速:启用 Swift-Syntax 预构建 (Prebuilts) Swift 模块化:使用 package 关键字实现 Target 间安全共享 Monorepo 实战:解决 VSCode/Cursor 中 SwiftLint 配置失效问题 VSCode/Cursor 测试 Core Data:手动编译 xcdatamodeld 指南
跨域传递 NSManagedObjectContext 为什么在 Swift 6.2 中不再报错?真正的变化不在编译器
东坡肘子 · 2026-03-04 · via 肘子的 Swift 记事本 | Fatbobman's Blog

当同一段与并发有关的代码在 Xcode 16 中无法通过,却能在 Xcode 26 中顺利编译时,你第一时间会想到什么?我最初的判断是编译器进化了,但现实并没有这么简单。本文将记录我最近遇到的一次有意思的排查过程:从测试失败出发,一步步追到 Core Data 的 SDK interface,最终发现,问题的关键并不完全在 Swift 编译器本身,而在 NSManagedObjectContext 被导入 Swift 的方式已经发生了变化。

问题

最近在为 Persistent History TrackingKit 2 补充和调整测试代码时,我遇到了一个特殊情况,为了模拟两个不同角色(例如主程序和扩展、两个不同进程中的组件)之间通过 Persistent History 合并变更的场景,我在测试中让两个不同域共同引用同一个 NSManagedObjectContext 实例:

let app2Handler = TestAppDataHandler( // actor domain
  container: container,
  context: app2Context,
  viewName: "App2Handler"
)

let kit = PersistentHistoryTrackingKit( // current domain
  container: container,
  contexts: [app2Context],
  currentAuthor: "App2",
  allAuthors: ["App1", "App2"],
  userDefaults: userDefaults,
  uniqueString: uniqueString,
  logLevel: 0,
  autoStart: false
)

完整的测试用例(包括 Actor 隔离与自定义执行器的实现)可查看实际的测试代码

上面的代码,在 Swift 6 模式下, Xcode 26.3(Swift 6.2.4)下可以正常编译,但在 Xcode 16.4(Swift 6.1.2)下却会直接被编译器拦住,给出典型并发安全错误:

sending 'app2Context' risks causing data races

根据我的最初理解,在 Swift 6.1.2 中,编译器认为这里存在风险:

  • app2Context 被送入了一个 actor 相关的隔离域
  • 当前作用域随后又继续使用了同一个 app2Context
  • 因此可能存在 “actor-isolated and local nonisolated uses” 的重叠访问

但从我的具体实现上看,与 context 有关的并发操作都是在其私有队列中进行的;而 TestAppDataHandler 又通过自定义 executor 将 actor 执行固定在这个 context 的执行环境中。从运行时语义上,这段代码理论上是安全的。

起初,我的第一反应是 Swift 6.2 对于 Region-Based Isolation(SE-0414)相关实现更成熟了,因此对 non-Sendable 值跨域传递的分析更准确,放过了 Swift 6.1 中的保守误报,为此我尝试在 6.2 版本中做进一步验证。

Swift 6.2 的并发分析进步了?

为了验证这一点,我先尝试构造一个不依赖 Core Data 的纯 Swift 版本重现。思路很简单:

  • 准备一个普通的 non-Sendable 引用类型
  • 把它传给一个 actor 的初始化器
  • 然后在当前作用域继续访问它
final class Token {
  var value: Int = 0
}

private actor TokenActor {
  init(token: Token) {}
}

func repro() {
  let token = Token()
  let actor = TokenActor(token: token)
  _ = token.value
  _ = actor
}

结果很快就推翻了我最初的判断:

  • Swift 6.1.2:报错
  • Swift 6.2.4:仍然报错

也就是说,Swift 6.2 并没有普遍放过“non-Sendable 值进入 actor 域后,当前作用域继续访问”的模式。

如果只是一般的 non-Sendable 引用类型,这两版编译器的判断是一致的。

在尝试了各种不同的 Token 类型设计后,我将目标逐渐锁定到 NSManagedObjectContext 这个类型本身,或许它有什么特别之处。

通过真实代码来锁定问题

既然不是一般的 non-Sendable 值,那我就反过来,从真实测试代码中持续删减,直到得到能稳定复现差异的最小骨架。

最后得到的版本只剩下这几行:

import CoreData

private actor ReducedContextActor {
  init(context: NSManagedObjectContext) {}
}

func repro() {
  let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
  let actor = ReducedContextActor(context: context)
  context.name = "StillLocal"
  _ = actor
}

具体结果如下:

  • Swift 6.1.2:报错
  • Swift 6.2.4:通过

也就是说,此时我们已经拿到了一个非常干净的 Core Data 特定重现。

在 Core Data 的 interface 中找寻答案

根据我之前对于 Core Data 的理解,只有 NSManagedObjectIDNSPersistentContainerNSPersistentStoreCoordinator 被声明为了 Sendable,难道 Xcode 26 中的 Core Data 有了其他的变化?

于是我开始对比 Xcode 16 和 26 自带的 Core Data 头文件和 Swift interface,很快,我就找到了决定性的差异。

在 Xcode 26.3 的 NSManagedObjectContext.h 中,类声明前多了这样一行:

NS_SWIFT_NONISOLATED NS_SWIFT_SENDABLE
@interface NSManagedObjectContext : NSObject <NSCoding, NSLocking>

而在 Xcode 16.4 的对应头文件中,这两个标记是不存在的。

除此之外,在 26.3 的 CoreData.swiftinterface 中,还能看到一系列新增的并发相关导入结果,例如:

convenience nonisolated public init(_ type: CoreData.NSManagedObjectContext.ConcurrencyType)
@preconcurrency nonisolated public func performAndWait<T>(...)
@preconcurrency nonisolated public func perform<T>(...)
nonisolated public func fetch<T>(...)
nonisolated public func count<T>(...)

这与 16.4 版本相比差异非常明显。

到这里,问题的答案已经基本浮出水面了:NSManagedObjectContext 在新 SDK 中被以更强的并发语义导入到了 Swift 中。

NS_SWIFT_SENDABLE 和 NS_SWIFT_NONISOLATED 到底是什么

为了避免凭感觉下结论,我继续追到了宏定义本身。

它们定义在 Foundation 的 NSObjCRuntime.h 中,对应定义如下:

// Indicates that the thing it is applied to should be imported as 'Sendable' in Swift:
// * Type declarations are imported into Swift with a 'Sendable' conformance.
// * Block parameters are imported into Swift with an '@Sendable' function type. (Write it in the same place you would put 'NS_NOESCAPE'.)
// * 'id' parameters are imported into Swift as 'Sendable', not 'Any'.
// * Other object-type parameters are imported into Swift with an '& Sendable' requirement.
#define NS_SWIFT_SENDABLE __attribute__((swift_attr("@Sendable")))

// Indicates that a specific member of an 'NS_SWIFT_UI_ACTOR'-isolated type is "threadsafe" and should be callable from outside the main actor.
#define NS_SWIFT_NONISOLATED __attribute__((swift_attr("nonisolated")))

如果继续往下看,紧接着还有:

// Indicates that a specific member of an 'NS_SWIFT_UI_ACTOR'-isolated type does its own data isolation management and does not participate in Swift concurrency checking.
#define NS_SWIFT_NONISOLATED_UNSAFE __attribute__((swift_attr("nonisolated(unsafe)")))

头文件中的注释给出了相当明确的说明。

  • NS_SWIFT_SENDABLE:它表示被标注的声明应当以 Sendable 的方式导入到 Swift 中。对于类型声明而言,效果就是:这个 Objective-C 类型在导入 Swift 时具有 Sendable 语义;对于 block 参数而言,效果就是:对应的 Swift 闭包会被导入为 @Sendable

  • NS_SWIFT_NONISOLATED:它表示被标注的成员应当以 nonisolated 的方式导入到 Swift 中。

    在注释里,这个宏被描述为针对某个 member 的标记。不过在 NSManagedObjectContext 上,Apple 实际上把它放在了类声明前,这意味着至少在导入器层面,它对整个类型的导入行为产生了更广泛的影响。

而实际导入结果已经表明:NSManagedObjectContext 在 Xcode 26 对应的 SDK 中,不再被当作一个普通的 non-Sendable Objective-C 类来处理。它同 NSManagedObjectIDNSPersistentContainerNSPersistentStoreCoordinator 一样现在都是 Sendable 类型了。

把这两个宏用到我们自己的 Objective-C 类上

尽管我们已经接近答案了,但我还是希望做最后一步验证:如果把 NS_SWIFT_SENDABLENS_SWIFT_NONISOLATED 挂到我自己的 Objective-C 类上,Swift 的行为会不会跟着变化?

为此我构造了两个本地 Objective-C 模块:

未标注版本

@interface ObjCPlainToken : NSObject
@property (nonatomic) NSInteger value;
@end

标注版本

NS_SWIFT_NONISOLATED NS_SWIFT_SENDABLE
@interface ObjCAnnotatedToken : NSObject
@property (nonatomic) NSInteger value;
@end

Swift 测试代码两边都保持同样的结构:

private actor TokenActor {
  init(token: ObjC...Token) {}
}

func repro() {
  let token = ObjC...Token()
  let actor = TokenActor(token: token)
  token.value = 1
  _ = actor
}

实验结果非常干脆:

  • 未标注版本
    • Swift 6.1.2:报错
    • Swift 6.2.4:报错
  • 标注版本
    • Swift 6.1.2:通过
    • Swift 6.2.4:通过

这一步几乎可以视为实锤:这两个宏确实能够改变这类诊断的结果。

NSManagedObjectContext 在新 SDK 中恰好获得了这两个标记。NSManagedObjectIDNSPersistentContainerNSPersistentStoreCoordinator 至少从 Xcode 16 时便已经使用了 NS_SWIFT_SENDABLE,但并没有像 NSManagedObjectContext 这样同时获得 NS_SWIFT_NONISOLATED

到这里,我们该如何重新理解最初的问题

现在可以给出一个更准确的结论了。

最初我把问题理解为:

  • Swift 6.1 过于保守
  • Swift 6.2 更聪明,因此放过了 NSManagedObjectContext

现在看来,更准确的说法应该是:

  1. 一般的 non-Sendable 引用类型,Swift 6.1 和 Swift 6.2 对这种模式的态度是一致的,都会报错。
  2. NSManagedObjectContext 在 Xcode 26 的 SDK 中,被以 NS_SWIFT_SENDABLENS_SWIFT_NONISOLATED 等更强的并发语义导入到了 Swift。
  3. 因此它在 Xcode 26 的 SDK 中不再是一个 non-Sendable Objective-C 类型而是一个 Sendable 类型。

看似是 Swift 编译器的进化,真正发生变化的,其实是框架的导入语义

结语

尽管 NSManagedObjectContext 现在可以跨域传递,但这并不意味着在生产环境中我们也应该这样使用。从 SwiftData 的 interface 来看,ModelContext 虽然有一个 @unchecked Sendable 扩展,但该扩展同时被标记为 unavailable,并明确写了 contexts cannot be shared across concurrency contexts。除非确实遇到需要跨域的场景,还是应该尽量遵循 Core Data 一贯的并发编码准则。

如果我在发现问题后第一时间检查 NSManagedObjectContext 的声明变化,或许很快就能查明真相。但正是因为先入为主地把问题归因于编译器,才有了这次一路追到 SDK 头文件的过程。这次经历也给了我一个提醒:当新旧版本的编译行为出现差异时,除了考虑编译器本身的变化,也值得去检查框架的导入语义是否同步发生了调整。很多时候,答案藏在你没有第一时间想到去看的地方。