Sendable和@Sendable是Swift5.5中并发变化的一部分,解决了结构化并发结构和执行器消息之间类型检查的挑战性问题。什么时候应该使用Sendable?Sendable协议和闭包指示那些传递值的公共API是否是线程安全的,以便将值传递给编译器。当没有公共修饰符、存在内部锁定系统或修饰符像值类型一样实现复制写入时,公共API可以安全地跨并发域使用。标准库中的许多类型已经支持Sendable协议,无需为许多类型添加一致性。由于标准库的支持,编译器可以为您的自定义类型创建隐式一致性。例如,整数类型支持协议:extensionInt:Sendable{}一旦我们创建了一个具有单个Int类型属性的值类型结构,我们就隐式地获得了对Sendable协议的支持。structArticle{varviews:Int}同时,相同的Article内容类,将不必隐式符合协议:classArticle{varviews:Int}不符合,因为它是引用类型,所以可以从其他并发域。也就是说,这类文章(Article)的传递不是线程安全的,因此编译器无法隐式地将其标记为符合Sendable协议。使用泛型和枚举时的隐式一致性很好理解,如果泛型不符合Sendable协议,编译器将不会向泛型添加隐式一致性。structContainer{varchild:Value}但是,如果我们将协议要求添加到我们的泛型中,我们将获得隐式支持:structContainer{varchild:Value}对于关联值同样如此enums:如果枚举值不符合Sendable协议,隐含的Sendable协议符合性没有影响。如您所见,我们自动从编译器中得到一个错误:'Sendable'-conforming枚举'State'的关联值'loggedIn(name:)'具有不可发送类型'(name:NSAttributedString)'。我们可以通过使用值类型String来解决这个错误,因为它已经符合Sendable。enumState:Sendable{caseloggedOutcaseloggedIn(name:String)}从线程安全实例中抛出错误同样的规则适用于想要符合Sendable的错误类型。structArticleSavingError:Error{varauthor:NonFinalAuthor}extensionArticleSavingError:Sendable{}由于author不是不可变的(非最终的)并且不是线程安全的(稍后会详细介绍),我们将遇到以下错误:Storedproperty'author符合“可发送”结构的“ArticleSavingError”具有不可发送类型“NonFinalAuthor”。您可以通过确保ArticleSavingError的所有成员都符合Sendable协议来修复此错误。隐式一致性消除了许多我们需要自己向Sendable协议添加一致性的情况。然而,在某些情况下,我们知道我们的类型是线程安全的,但编译器不会为我们添加隐式一致性。一个常见的例子是一个标记为不可变且内部具有锁定机制的类:finalclassUser:Sendable{letname:Stringinit(name:String){self.name=name}}你需要用@标记unchecked属性Mutate表示由于内部锁定机制,我们的类是线程安全的::String=""funcupdateName(_name:String){DispatchQueue.userMutatingLock.sync{self.name=name}}}对Sendable协议的一致性必须出现在同一个源文件中,以确保编译器检查所??有可见成员线程安全的。例如,您可以在Swift包等模块中定义以下类型:publicstructArticle{internalvartitle:String}Article是公开的,而title是内部的,在模块外不可见。因此,编译器无法在源文件之外应用Sendable一致性,因为它对title属性不可见,即使title使用符合Sendable协议的String类型。当我们想让一个可变的非最终类符合Sendable协议时,也会出现同样的问题:一个可变的非最终类不能符合Sendable协议。由于类是非final的,我们不能遵守Sendable协议,因为我们不确定其他类是否会继承User的非Sendable成员。结果,我们得到以下错误:Non-finalclass‘User’cannotconformtoSendable;使用@uncheckedSendable。如您所见,编译器建议使用@uncheckedSendable。我们可以将此属性添加到我们的User类并消除此错误:你继承自User,确保它是线程安全的。由于我们对自己和同事负有额外责任,我不鼓励使用此属性并建议使用组合、最终类或值类型来达到我们的目的。函数可以跨并发域传递,因此也需要可调度的一致性。但是,函数不能遵循协议,所以Swift引入了@Sendable属性。您可以传递的函数示例包括全局函数声明、闭包和访问器(例如getter和setter)。SE-302的部分动机是执行尽可能少的同步。我们希望这样一个系统中的绝大多数代码都是异步的。使用@Sendable属性,我们是在告诉编译器他不需要额外的同步,因为闭包中所有捕获的值都是线程安全的。一个典型的例子是在Actor隔离中使用闭包。actorArticlesList{funcfilteredArticles(_isIncluded:@Sendable(Article)->Bool)async->[Article]{}}如果使用非Sendabel闭包,我们会遇到错误:letlistOfArticles=ArticlesList()varsearchKeyword:NSAttributedString?=NSAttributedString(string:"keyword")letfilteredArticles=awaitlistOfArticles.filteredArticles{articleinguardletsearchKeyword=searchKeywordelse{returnfalse}returnarticle.title==searchKeyword.string}当然,我们可以使用一个普通的String来快速解决这种情况,但它显示了编译器如何帮助我们加强线程安全。Xcode14允许您使用SWIFT_STRICT_CONCURRENCY构建设置启用严格的并发检查。启用严格的并发检查以修复Sendable合规性。此构建设置控制编译器执行Sendable和actor-isolation检查的级别:Minimal:编译器将仅诊断明确标记为Sendable一致性的实例,等同于Swift5.5和5.6的行为。不会有警告或错误。有针对性:强制执行可发送约束并使用异步/等待对所有并发代码执行参与者隔离检查。编译器还将检查显式采用Sendable的实例。这种模式试图在与现有代码的兼容性和捕捉潜在的数据竞争之间取得平衡。完成:匹配预期的Swift6语义以检查和消除数据竞争。此模式检查其他两种模式所做的一切,并将这些检查应用于项目中的所有代码。严格的并发检查构建设置帮助Swift走向数据竞争安全。与此构建设置相关的每个触发警告都可能表明您的代码中存在潜在的数据竞争。因此,您必须考虑启用严格的并发检查来验证您的代码。在Xcode14中启用严格并发您将收到的警告数量取决于您在项目中使用并发的频率。对于StockAnalyzer,我有大约17个警告需要解决:指示潜在数据竞争的并发相关警告。这些警告可能令人望而生畏,但根据本文中的知识,您应该能够摆脱其中的大部分警告并防止数据竞争的发生。但是,有些警告是您无法控制的,因为它们是由外部模块触发的。在我的例子中,我有一个与SWHighlight相关的警告,它不符合Sendable,而Apple在他们的SharedWithYou框架中定义了它。在上面的SharedWithYou框架的示例中,最好等待库的所有者添加Sendable支持。在这种情况下,这意味着等待Apple为SWHighlight实例指定Sendable一致性。对于这些库,您可以使用@preconcurrency属性暂时禁用Sendable警告:来自这些库的代码仍然受到数据竞争的影响。如果您正在使用这些框架的实例,则需要考虑这些实例是否实际上是线程安全的。一旦您使用的框架更新为符合Sendable,您就可以删除@preconcurrency属性并修复可能触发的警告。