
引子
雪山顶上,寒风如刀。
胡斐手持一部《Swift 6.2 心法》,眉头拧成了疙瘩 ------ 前日他为解码雪山派传承的「数据秘籍」,用了nonisolated函数,却忽在主线程卡顿,忽又在后台线程乱走,险些让田归农趁机盗走秘籍。

正当他愁眉不展时,一道苍劲的声音从身后传来:「胡兄弟,此乃并发之惑也,若想破局,需先懂nonisolated 的根基,再悟 @concurrent 的妙谛。」
在本篇江湖夜话中,各位少侠将学到如下内容:
- 引子
- 📜 第一回:nonisolated 函数的「门派归属」之谜
- 🧩 第二回:nonisolated (nonsending):给独行客立「规矩」
- ⚡ 第三回:@concurrent 登场:定向「派活」的武林令牌
- 🎯 第四回:何时用 @concurrent?江湖人的「分寸感」
- 🔚 尾声:令牌入鞘,心法永存
来者正是「打遍天下无敌手」的苗人凤,身旁还站着医术与代码双绝的程灵素。三人围坐火塘,一场关于 Swift 6.2 并发江湖的讲解,就此展开。
📜 第一回:nonisolated 函数的「门派归属」之谜
苗人凤指尖划过《Swift 心法》,开门见山:「胡兄弟,你可知nonisolated 函数为何『性情不定』?此函数本就不属任何『actor 门派』,在 Swift 6.1 或 6.2 默认设置下,它就像江湖中游荡的独行客,去哪干活全凭规矩 ------ 若是带了async关键字,便会直奔『全局执行器』(背景线程);若是没带,就跟着调用它的『门派弟子』(调用者 actor)走。」

程灵素一旁补充,随手在火塘边的石片上写了段代码:
swift
// 带async的nonisolated函数:如同独行客去后山练功,绝不占主线程(主actor)
nonisolated
func decode<T: Decodable>(_ data: Data) async throws -> T {
// 此处解码操作,全程在背景线程执行,主线程可安心处理用户交互(比如接招防御)
}
// 不带async的nonisolated函数:跟着调用者走,调用者在主actor,它就去主actor
nonisolated
func decode<T: Decodable>(_ data: Data) throws -> T {
// 若从主actor调用(比如主线程处理UI时调用),此函数就会在主actor运行,可能造成卡顿
}
胡斐一拍大腿:「原来如此!前日我解码时,先调用了带 async 的版本,后台跑得顺畅;后来删了 async,却在主 actor 卡住,让田归农钻了空子 ------ 这差别也太让人一头雾水了!」
🧩 第二回:nonisolated (nonsending):给独行客立「规矩」
苗人凤闻言点头:「此乃 Swift 江湖的旧疾,故 6.2 版本新增了nonisolated(nonsending) 这一『规矩』,专为统一函数的『行事风格』而来。」
他指着石片上的新代码,解释道:「凡是标了nonisolated(nonsending) 的函数,不管带不带async ,都像入了『固定门派』,永远跟着调用者的『执行器』走 ------ 调用者在主 actor,它就去主 actor;调用者在后台 actor,它就去后台。这样一来,函数的行为便一目了然,再无之前的混乱。」

程灵素接过话头,语气中带着一丝严谨:「更重要的是,它能减少『并发风险』。你想,若函数总往『全局执行器』跑,就像不断开辟新的『练功场』(隔离域),多个线程同时碰同一个『秘籍数据』(状态),很容易造成数据错乱 ------ 而nonisolated(nonsending) 让函数留在原『练功场』,数据不用跨域传递,自然不用强制遵守Sendable协议,省了不少麻烦。」
她又写下一段代码,标注得清清楚楚:
swift
// nonisolated(nonsending)函数:规矩森严,永远跟着调用者走
nonisolated(nonsending)
func decode<T: Decodable>(_ data: Data) async throws -> T {
// 无论是否有async,调用者在哪个actor,此函数就跑在哪个actor
// 避免开辟新隔离域,减少数据并发访问的风险,代码逻辑也更易梳理
}
胡斐若有所思:「如此说来,若把所有 nonisolated 函数都设为默认nonsending,岂不是更省心?」
「正是!」苗人凤抚须而笑,「Swift 6.2 有个『NonIsolatedNonSendingByDefault』开关,打开后所有显式或隐式的 nonisolated 函数,都会默认遵循nonsending规矩。只是这般一来,若真要让函数去『全局执行器』干活,又该如何?」

话音刚落,程灵素眼中闪过一丝亮光:「这便要请出今日的主角 ------@concurrent了。」
⚡ 第三回:@concurrent 登场:定向「派活」的武林令牌
苗人凤站起身,拿起一块刻着「并发」二字的木牌,郑重道:「胡兄弟,@concurrent 就像这令牌 ------ 持有它的函数,会被明确派往『全局执行器』(背景线程),且自动获得nonisolated身份,无需你再额外标注。」
他随即在石片上写下关键代码,每一笔都力透石背:
swift
// @concurrent函数:自带「去全局执行器」令牌,无需额外写nonisolated
@concurrent
func decode<T: Decodable>(_ data: Data) async throws -> T {
// 此函数必定在背景线程执行,专治「主线程卡顿」的毛病
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
}
「更妙的是,它还能在『门派内部』使用。」程灵素补充道,比如主 actor 或自定义 actor 中的函数,只要没明确标注隔离属性,都能挂这令牌:
swift
// 主actor中的@concurrent函数:虽在主actor类里,却去背景线程干活
@MainActor
class DataViewModel {
@concurrent
func decode<T: Decodable>(_ data: Data) async throws -> T {
// 类本身属主actor,但此函数靠@concurrent去了背景线程,不阻塞UI
}
}
// 自定义actor中的@concurrent函数:同理,脱离actor去全局执行器
actor DataViewModel {
@concurrent
func decode<T: Decodable>(_ data: Data) async throws -> T {
// 不占用actor的资源,让actor专注处理其他任务
}
}
胡斐忽然问道:「那若是函数已经明确了隔离属性,比如标了 **@MainActor ** 或 nonisolated (nonsending),还能挂这令牌吗?」

苗人凤摇头,语气严肃:「万万不可!这就像一个人已经入了『武当派』,又怎能同时拿『少林令牌』?两者隔离规则冲突,Swift 江湖绝不允许这般混乱。」他指着石片上的错误示例说道:
swift
// 错误1:@concurrent与@MainActor冲突,一个要去全局,一个要留主actor
@concurrent @MainActor
func decode\<T: Decodable>(\_ data: Data) async throws -> T { /\* ... \*/ }
// 错误2:@concurrent与nonisolated(nonsending)冲突,一个去全局,一个跟调用者
@concurrent nonisolated(nonsending)
func decode\<T: Decodable>(\_ data: Data) async throws -> T { /\* ... \*/ }
🎯 第四回:何时用 @concurrent?江湖人的「分寸感」
火塘的柴火噼啪作响,胡斐看着手中的令牌,又问:「既然 @concurrent 能去背景线程,那是不是所有函数都该挂这令牌?」
程灵素闻言轻笑:「胡兄弟,你这是把并发当『蛮力』了。江湖中,武功并非越多越好,而是要恰到好处------@concurrent 也是如此,用对了是救场,用错了反成累赘。」

她举了个例子:比如网络请求函数,本身带await,调用时会「暂停」,让调用者去做其他事,根本不用 @concurrent:
swift
class Networking {
// 网络请求函数:自带await,调用时会暂停,主actor可趁机处理用户输入
func loadData(from url: URL) async throws -> Data {
let (data, response) = try await URLSession.shared.data(from: url)
// 等待网络响应时,主actor不会被阻塞,无需@concurrent
return data
}
}
苗人凤接过话头,继续道:「但若是遇到『耗时操作』,比如解码大量数据,@concurrent 就该出手了。你看这段代码 ------」
swift
class Networking {
// 普通函数:跟着调用者跑,若在主actor会卡顿
func getFeed() async throws -> Feed {
let data = try await loadData(from: Feed.endpoint)
// 若decode在主actor,解码大量数据时会阻塞UI
let feed: Feed = try await decode(data)
return feed
}
// 加上@concurrent:去背景线程解码,不卡主actor
@concurrent
func decode<T: Decodable>(_ data: Data) async throws -> T {
let decoder = JSONDecoder()
// 耗时的解码操作在背景线程执行,主actor安心处理UI
return try decoder.decode(T.self, from: data)
}
}
他顿了顿,目光深邃:「胡兄弟,记住 ------ 并发的真谛不是『越多越好』,而是『按需分配』。就像你练『胡家刀法』,不是每一招都用尽全力,而是该快则快,该稳则稳。@concurrent 只该用在『真需要并发』的地方,比如耗时的计算、解码。而其余时候,守着nonisolated(nonsending) 的规矩,反而让代码更稳、更少 bug。」
🔚 尾声:令牌入鞘,心法永存
雪风渐歇,火塘的余温暖了三人的心。
胡斐收起《Swift 心法》,手中的 @concurrent「令牌」仿佛有了重量 ------ 他终于明白,Swift 并发不是炫技的工具,而是像江湖规矩一样,守得住分寸,才能写出「稳如泰山」的代码。
苗人凤看着他的神情,微微一笑:「江湖路远,代码亦如武功。今日这 @concurrent 的道理,你若记牢了,日后面对再复杂的并发难题,也能游刃有余。」
程灵素补充道:「更重要的是,这心法还能『未雨绸缪』------ 如今用 @concurrent 标记函数,不仅明确了你的意图,更是为未来的 Swift 版本铺路,算得上是『一举两得』。」

胡斐拱手致谢,转身望向雪山深处。此刻他心中已无困惑,只待明日用新学的并发心法,重解雪山秘籍,让田归农之流再无可乘之机。而那 @concurrent 的令牌,也已悄悄入鞘,等待着在真正需要它的时刻,再展锋芒。
------ 毕竟,真正的江湖高手,从不会为了用武功而用武功;真正的程序员,也从不会为了用并发而用并发。
那么,各位秃头少侠你们也了然了吗?
感谢观赏,我们下次再会吧!8-)