我直接在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)
时,即使latest
是String?
,它也会自动将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
基本上,通过给一个可选属性赋值,你决定了整个管道的行为,如果一切顺利,没有类型不匹配,你可以得到意外的行为(不是不正确的行为,只是意外的)。