Swift 并发修改Sendable 闭包实例详解
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>前言</li><li>使用 Sendable</li><ul class="second_class_ul"><li>使用泛型和枚举时的隐式一致性</li><li>从线程安全的实例中抛出错误</li></ul><li>如何使用Sendable协议</li><ul class="second_class_ul"></ul><li>要在同一源文件中遵守 Sendable的限制</li><ul class="second_class_ul"></ul><li>如何使用 @Sendabele</li><ul class="second_class_ul"></ul><li>Swift 6: 为你的代码启用严格的并发性检查</li><ul class="second_class_ul"><li>Enabling strict concurrency in Xcode 14</li></ul></ul></div><p class="maodian"></p><h2>前言</h2><p><code>Sendable</code> 和 <code>@Sendable</code> 是 Swift 5.5 中的并发修改的一部分,解决了结构化的并发结构体和执行者消息之间传递的类型检查的挑战性问题。</p>
<p class="maodian"></p><h2>使用 Sendable</h2>
<p>应该在什么时候使用 <code>Sendable</code>?</p>
<p><code>Sendable</code>协议和闭包表明那些传递的值的公共API是否线程安全的向编译器传递了值。当没有公共修改器、有内部锁定系统或修改器实现了与值类型一样的复制写入时,公共API可以安全地跨并发域使用。</p>
<p>标准库中的许多类型已经支持了<code>Sendable</code>协议,消除了对许多类型添加一致性的要求。由于标准库的支持,编译器可以为你的自定义类型创建隐式一致性。</p>
<p>例如,整型支持该协议:</p>
<div class="jb51code"><pre class="brush:cpp;">extension Int: Sendable {}
</pre></div>
<p>一旦我们创建了一个具有 <code>Int</code> 类型的单一属性的值类型结构体,我们就隐式地得到了对 <code>Sendable</code> 协议的支持。</p>
<div class="jb51code"><pre class="brush:cpp;">// 隐式地遵守了 Sendable 协议
struct Article {
var views: Int
}
</pre></div>
<p>与此同时,同样的 <code>Article</code> 内容的类,将不会有隐式遵守该协议:</p>
<div class="jb51code"><pre class="brush:cpp;">// 不会隐式的遵守 Sendable 协议
class Article {
var views: Int
}
</pre></div>
<p>类不符合要求,因为它是一个引用类型,因此可以从其他并发域变异。换句话说,该类文章(<code>Article</code>)的传递不是线程安全的,所以编译器不能隐式地将其标记为遵守<code>Sendable</code>协议。</p>
<p class="maodian"></p><h3>使用泛型和枚举时的隐式一致性</h3>
<p>很好理解的是,如果泛型不符合<code>Sendable</code>协议,编译器就不会为泛型添加隐式的一致性。</p>
<div class="jb51code"><pre class="brush:cpp;">// 因为 Value 没有遵守 Sendable 协议,所以 Container 也不会自动的隐式遵守该协议
struct Container<Value> {
var child: Value
}
</pre></div>
<p>然而,如果我们将协议要求添加到我们的泛型中,我们将得到隐式支持:</p>
<div class="jb51code"><pre class="brush:cpp;">// Container 隐式地符合 Sendable,因为它的所有公共属性也是如此。
struct Container<Value: Sendable> {
var child: Value
}
</pre></div>
<p>对于有关联值的枚举也是如此:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/2022101308414104.jpg" /></p>
<p>你可以看到,我们自动从编译器中得到一个错误:</p>
<blockquote><p>Associated value ‘loggedIn(name:)’ of ‘Sendable’-conforming enum ‘State’ has non-sendable type ‘(name: NSAttributedString)’</p></blockquote>
<p>我们可以通过使用一个值类型<code>String</code>来解决这个错误,因为它已经符合<code>Sendable</code>。</p>
<div class="jb51code"><pre class="brush:cpp;">enum State: Sendable {
case loggedOut
case loggedIn(name: String)
}
</pre></div>
<p class="maodian"></p><h3>从线程安全的实例中抛出错误</h3>
<p>同样的规则适用于想要符合<code>Sendable</code>的错误类型。</p>
<div class="jb51code"><pre class="brush:cpp;">struct ArticleSavingError: Error {
var author: NonFinalAuthor
}
extension ArticleSavingError: Sendable { }
</pre></div>
<p>由于作者不是不变的(non-final),而且不是线程安全的(后面会详细介绍),我们会遇到以下错误:</p>
<blockquote><p>Stored property ‘author’ of ‘Sendable’-conforming struct ‘ArticleSavingError’ has non-sendable type ‘NonFinalAuthor’</p></blockquote>
<p>你可以通过确保<code>ArticleSavingError</code>的所有成员都符合<code>Sendable</code>协议来解决这个错误。</p>
<p class="maodian"></p><h2>如何使用Sendable协议</h2>
<p>隐式一致性消除了很多我们需要自己为<code>Sendable</code>协议添加一致性的情况。然而,在有些情况下,我们知道我们的类型是线程安全的,但是编译器并没有为我们添加隐式一致性。</p>
<p>常见的例子是被标记为不可变和内部具有锁定机制的类:</p>
<div class="jb51code"><pre class="brush:cpp;">/// User 是不可改变的,因此是线程安全的,所以可以遵守 Sendable 协议
final class User: Sendable {
let name: String
init(name: String) { self.name = name }
}
</pre></div>
<p>你需要用<code>@unchecked</code>属性来标记可变类,以表明我们的类由于内部锁定机制所以是线程安全的:</p>
<div class="jb51code"><pre class="brush:cpp;">extension DispatchQueue {
static let userMutatingLock = DispatchQueue(label: "person.lock.queue")
}
final class MutableUser: @unchecked Sendable {
private var name: String = ""
func updateName(_ name: String) {
DispatchQueue.userMutatingLock.sync {
self.name = name
}
}
}
</pre></div>
<p class="maodian"></p><h2>要在同一源文件中遵守 Sendable的限制</h2>
<p><code>Sendable</code>协议的一致性必须发生在同一个源文件中,以确保编译器检查所有可见成员的线程安全。</p>
<p>例如,你可以在例如 Swift package这样的模块中定义以下类型:</p>
<div class="jb51code"><pre class="brush:cpp;">public struct Article {
internal var title: String
}
</pre></div>
<p><code>Article</code> 是公开的,而标题<code>title</code>是内部的,在模块外不可见。因此,编译器不能在源文件之外应用<code>Sendable</code>一致性,因为它对标题属性不可见,即使标题使用的是遵守<code>Sendable</code>协议的<code>String</code>类型。</p>
<p>同样的问题发生在我们想要使一个可变的非最终类遵守<code>Sendable</code>协议时:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/2022101308414105.jpg" /></p>
<p>由于该类是非最终的,我们无法符合<code>Sendable</code>协议的要求,因为我们不确定其他类是否会继承<code>User</code>的非<code>Sendable</code>成员。因此,我们会遇到以下错误:</p>
<blockquote><p>Non-final class ‘User’ cannot conform to Sendable; use @unchecked Sendable</p></blockquote>
<p>正如你所看到的,编译器建议使用<code>@unchecked Sendable</code>。我们可以把这个属性添加到我们的<code>User</code>类中,并摆脱这个错误:</p>
<div class="jb51code"><pre class="brush:cpp;">class User: @unchecked Sendable {
let name: String
init(name: String) { self.name = name }
}
</pre></div>
<p>然而,这确实要求我们无论何时从<code>User</code>继承,都要确保它是线程安全的。由于我们给自己和同事增加了额外的责任,我不鼓励使用这个属性,建议使用组合、最终类或值类型来实现我们的目的。</p>
<p class="maodian"></p><h2>如何使用 @Sendabele</h2>
<p>函数可以跨并发域传递,因此也需要可发送的一致性。然而,函数不能符合协议,所以Swift引入了<code>@Sendable</code>属性。你可以传递的函数的例子是全局函数声明、闭包和访问器,如<code>getters</code>和<code>setters</code>。</p>
<p>SE-302的部分动机是执行尽可能少的同步</p>
<p>我们希望这样一个系统中的绝大多数代码都是无同步的。</p>
<p>使用<code>@Sendable</code>属性,我们将告诉编译器,他不需要额外的同步,因为闭包中所有捕获的值都是线程安全的。一个典型的例子是在Actor isolation中使用闭包。</p>
<div class="jb51code"><pre class="brush:cpp;">actor ArticlesList {
func filteredArticles(_ isIncluded: @Sendable (Article) -> Bool) async -> {
// ...
}
}
</pre></div>
<p>如果你用非 <code>Sendabel</code> 类型的闭包,我们会遇到一个错误:</p>
<div class="jb51code"><pre class="brush:cpp;">let listOfArticles = ArticlesList()
var searchKeyword: NSAttributedString? = NSAttributedString(string: "keyword")
let filteredArticles = await listOfArticles.filteredArticles { article in
// Error: Reference to captured var 'searchKeyword' in concurrently-executing code
guard let searchKeyword = searchKeyword else { return false }
return article.title == searchKeyword.string
}
</pre></div>
<p>当然,我们可以通过使用一个普通的<code>String</code>来快速解决这种情况,但它展示了编译器如何帮助我们执行线程安全。</p>
<p class="maodian"></p><h2>Swift 6: 为你的代码启用严格的并发性检查</h2>
<p>Xcode 14 允许您通过 <code>SWIFT_STRICT_CONCURRENCY</code> 构建设置启用严格的并发性检查。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/2022101308414106.jpg" /></p>
<p>这个构建设置控制编译器对<code>Sendable</code>和<code>actor-isolation</code>检查的执行水平:</p>
<ul><li>Minimal : 编译器将只诊断明确标有<code>Sendable</code>一致性的实例,并等同于Swift 5.5和5.6的行为。不会有任何警告或错误。</li><li>Targeted: 强制执行<code>Sendable</code>约束,并对你所有采用<code>async/await</code>等并发的代码进行<code>actor-isolation</code>检查。编译器还将检查明确采用<code>Sendable</code>的实例。这种模式试图在与现有代码的兼容性和捕捉潜在的数据竞赛之间取得平衡。</li><li>Complete: 匹配预期的 Swift 6语义,以检查和消除数据竞赛。这种模式检查其他两种模式所做的一切,并对你项目中的所有代码进行这些检查。</li></ul>
<p>严格的并发检查构建设置有助于 Swift 向数据竞赛安全迈进。与此构建设置相关的每一个触发的警告都可能表明你的代码中存在潜在的数据竞赛。因此,必须考虑启用严格并发检查来验证你的代码。</p>
<p class="maodian"></p><h3>Enabling strict concurrency in Xcode 14</h3>
<p>你会得到的警告数量取决于你在项目中使用并发的频率。对于Stock Analyzer,我有大约17个警告需要解决:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/2022101308414107.jpg" /></p>
<p>这些警告可能让人望而生畏,但利用本文的知识,你应该能够摆脱大部分警告,防止数据竞赛的发生。然而,有些警告是你无法控制的,因为是外部模块触发了它们。在我的例子中,我有一个与<code>SWHighlight</code>有关的警告,它不符合<code>Sendable</code>,而苹果在他们的<code>SharedWithYou</code>框架中定义了它。</p>
<p>在上述<code>SharedWithYou</code>框架的例子中,最好是等待库的所有者添加<code>Sendable</code>支持。在这种情况下,这就意味着要等待苹果公司为<code>SWHighlight</code>实例指明<code>Sendable</code>的一致性。对于这些库,你可以通过使用<code>@preconcurrency</code>属性来暂时禁用<code>Sendable</code>警告:</p>
<div class="jb51code"><pre class="brush:cpp;">@preconcurrency import SharedWithYou
</pre></div>
<p>重要的是要明白,我们并没有解决这些警告,而只是禁用了它们。来自这些库的代码仍然有可能发生数据竞赛。如果你正在使用这些框架的实例,你需要考虑实例是否真的是线程安全的。一旦你使用的框架被更新为<code>Sendable</code>的一致性,你可以删除<code>@preconcurrency</code>属性,并修复可能触发的警告。</p>
<p>以上就是Swift 并发修改Sendable 闭包实例详解的详细内容,更多关于Swift 并发修改Sendable 闭包的资料请关注琼殿技术社区其它相关文章!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>Swift之for循环的基础使用学习</li><li>Swift简单快速的动态更换app图标AppIcon方法示例</li><li>Swift自动调整视图布局AutoLayout和AutoresizingMask功能详解</li><li>Swift 中 Opaque Types学习指南</li><li>Swift 中的 RegexBuilder学习指南</li><li>Swift中的高阶函数功能作用示例详解</li><li>Swift并发系统并行运行多个任务使用详解</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]