Swift Combine compactMap does not work (Xcode 13)



我直接在compactMap之后使用Combine的assign(to:)得到了一个意想不到的结果。这里的代码,它是100%可复制的Xcodes Playground, Xcode Version 13.0 (13A233)。

import Combine
import Foundation
class Test {
@Published var current: String?
@Published var latest1: String?
@Published var latest2: String?
@Published var latest3: String?

init() {
// Broken? - compactMap passes nil downstream
self.$current
.compactMap { $0 }
.assign(to: &self.$latest1)

// Fixed by specifying the transform closure type explicitely
self.$current
.compactMap { value -> String? in value }
.assign(to: &self.$latest2)

// Fixed by an additional map inbetween
self.$current
.compactMap { $0 }
.map { $0 }
.assign(to: &self.$latest3)
}
}
let obj = Test()
obj.current = "success"
print("current: (String(describing: obj.current))")
print("latest1: (String(describing: obj.latest1))")
print("latest2: (String(describing: obj.latest2))")
print("latest3: (String(describing: obj.latest3))")
print("")
obj.current = nil
print("current: (String(describing: obj.current))")
print("latest1: (String(describing: obj.latest1))") // nil shouldn't arrive here
print("latest2: (String(describing: obj.latest2))")
print("latest3: (String(describing: obj.latest3))")
// Output:
//current: Optional("success")
//latest1: Optional("success")
//latest2: Optional("success")
//latest3: Optional("success")
//
//current: nil
//latest1: nil
//latest2: Optional("success")
//latest3: Optional("success")

也许我错过了一些明显的东西?或者这是联合收割机的一个bug ?谢谢你的关注。


更新:我用一个更简洁的版本更新了示例代码

这里的问题是Swift的类型推断机制,Combine没有bug。我来解释一下。

compactMap(transform:)Value?映射到T,但是在您的情况下,T(self.latest的类型)实际上是String?,又名字符串可选。因此,整个管道被重新路由到String?输出,该输出与您在屏幕上看到的匹配。

当你没有指定所涉及的类型时,Swift会急于满足你的代码需求,所以当它看到assign(to: &self.$latest)时,即使latestString?,它也会自动将compactMap(String?) -> String路由到(String?) -> String?。用这种变换几乎不可能得到nil值:)

基本上,"有问题的"管道有以下类型推断:

self.$current
.compactMap { (val: String?) -> String? in return val }
.assign(to: &self.$latest) 

,这是因为在Swift中compactMap从一个可选对象到另一个可选对象是完全有效的。

试着写@Published var latest: String = "",你会得到预期的行为。注意,我说的是"预期",而不是"正确",就像"正确"一样。这取决于你的代码看起来像什么

同时,尝试将compactMap管道分成两部分:

let pub = self.$current.compactMap { $0 }
pub.assign(to: &self.$latest) // error: Inout argument could be set to a value with a type other than 'Published<String?>.Publisher'; use a value declared as type 'Published<String>.Publisher' instead

基本上,通过给一个可选属性赋值,你决定了整个管道的行为,如果一切顺利,没有类型不匹配,你可以得到意外的行为(不是不正确的行为,只是意外的)。

最新更新