存在地 :在阅读之前,了解您不能将 UIImage 分配给 ImageView socket 的 image
会有所帮助。属性通过 key 路径 \UIImageView.image
.这是属性(property):
@IBOutlet weak var iv: UIImageView!
现在,这会编译吗?
let im = UIImage()
let kp = \UIImageView.image
self.iv[keyPath:kp] = im // error
不!
Value of optional type 'UIImage?' must be unwrapped to a value of type 'UIImage'
好的,现在我们已经为实际用例做好了准备。
我真正想了解的是Combine 框架如何
.assign
订阅者在幕后工作。为了进行实验,我尝试使用我自己的 Assign 对象。在我的示例中,我的发布者管道生成一个 UIImage 对象,并将其分配给 image
UIImageView 属性的属性 self.iv
.如果我们使用
.assign
方法,这编译和工作:URLSession.shared.dataTaskPublisher(for: url)
.map {$0.data}
.replaceError(with: Data())
.compactMap { UIImage(data:$0) }
.receive(on: DispatchQueue.main)
.assign(to: \.image, on: self.iv)
.store(in:&self.storage)
所以,我对自己说,要看看这是如何工作的,我将删除
.assign
并用我自己的 Assign 对象替换它:let pub = URLSession.shared.dataTaskPublisher(for: url)
.map {$0.data}
.replaceError(with: Data())
.compactMap { UIImage(data:$0) }
.receive(on: DispatchQueue.main)
let assign = Subscribers.Assign(object: self.iv, keyPath: \UIImageView.image)
pub.subscribe(assign) // error
// (and we will then wrap in AnyCancellable and store)
啪!我们不能这样做,因为
UIImageView.image
是一个 optional UIImage,我的发布者生成了一个简单明了的 UIImage。我试图通过在关键路径中解开 Optional 来解决这个问题:
let assign = Subscribers.Assign(object: self.iv, keyPath: \UIImageView.image!)
pub.subscribe(assign)
酷,编译。但它在运行时崩溃,大概是因为 ImageView 的图像最初是
nil
.现在我可以通过添加
map
来解决所有这些问题。到我的管道,将 UIImage 包装在一个 Optional 中,以便所有类型都正确匹配。但我的问题是,这到底是如何运作的?我的意思是,为什么我不必在我使用的第一个代码中这样做 .assign
?为什么我可以指定 .image
关键路径在那里?关于关键路径如何与 Optional 属性一起工作似乎有一些技巧,但我不知道它是什么。在 Martin R 的一些输入之后,我意识到如果我们输入
pub
明确作为生产 UIImage?
我们得到与添加 map
相同的效果将 UIImage 包装在 Optional 中。所以这编译和工作let pub : AnyPublisher<UIImage?,Never> = URLSession.shared.dataTaskPublisher(for: url)
.map {$0.data}
.replaceError(with: Data())
.compactMap { UIImage(data:$0) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
let assign = Subscribers.Assign(object: self.iv, keyPath: \UIImageView.image)
pub.subscribe(assign)
let any = AnyCancellable(assign)
any.store(in:&self.storage)
这还是没有解释原来的
.assign
是怎么回事作品。似乎可以将类型的 optional 性插入管道中 .receive
运营商。但我不明白这怎么可能。
最佳答案
你(马特)可能至少已经知道其中的一些,但这里有一些事实供其他读者引用:
T
的对象输入 Optional<T>
, 如有必要进行语句类型检查。 (A) -> B
的闭包输入 (A) -> B?
.换句话说,这编译:let a: (Data) -> UIImage? = { UIImage(data: $0) }
let b: (Data) -> UIImage?? = a
这对我来说是一个惊喜。我在调查你的问题时发现了它。
现在让我们考虑使用
assign
:let p0 = Just(Data())
.compactMap { UIImage(data: $0) }
.receive(on: DispatchQueue.main)
.assign(to: \.image, on: self.iv)
Swift 同时对整个语句进行类型检查。自
\UIImageView.image
的 Value
类型是 UIImage?
, 和 self.iv
的类型是 UIImageView!
, Swift 必须做两件“自动”的事情来让这个语句进行类型检查:{ UIImage(data: $0) }
来自类型 (Data) -> UIImage?
输入 (Data) -> UIImage??
以便 compactMap
可以剥离一层Optional
并制作 Output
类型是 UIImage?
. iv
, 因为 Optional<UIImage>
没有名为 image
的属性,但是 UIImage
做。 这两个操作让 Swift 成功地对语句进行类型检查。
现在假设我们把它分成三个语句:
let p1 = Just(Data())
.compactMap { UIImage(data: $0) }
.receive(on: DispatchQueue.main)
let a1 = Subscribers.Assign(object: self.iv, keyPath: \.image)
p1.subscribe(a1)
Swift 首先对
let p1
进行类型检查陈述。不需要提升闭包类型,所以可以推导出Output
UIImage
的类型.然后 Swift 对
let a1
进行类型检查陈述。它必须隐式解包 iv
,但不需要任何 Optional
晋升。它推导出 Input
输入为 UIImage?
因为那是 Value
key 路径的类型。最后,Swift 尝试对
subscribe
进行类型检查。陈述。 Output
p1
的类型是 UIImage
,以及 Input
a1
的类型是 UIImage?
.它们是不同的,因此 Swift 无法成功地对语句进行类型检查。 Swift 不支持 Optional
提升泛型类型参数,如 Input
和 Output
.所以这不编译。我们可以通过强制
Output
来进行这种类型检查。 p1
的类型成为 UIImage?
:let p1: AnyPublisher<UIImage?, Never> = Just(Data())
.compactMap { UIImage(data: $0) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
let a1 = Subscribers.Assign(object: self.iv, keyPath: \.image)
p1.subscribe(a1)
在这里,我们强制 Swift 提升闭包类型。我用过
eraseToAnyPublisher
因为否则 p1
的类型太难看,无法拼出。自
Subscribers.Assign.init
是公开的,我们也可以直接使用它让 Swift 推断所有类型:let p2 = Just(Data())
.compactMap { UIImage(data: $0) }
.receive(on: DispatchQueue.main)
.subscribe(Subscribers.Assign(object: self.iv, keyPath: \.image))
Swift 类型检查成功。它与使用
.assign
的语句基本相同。早些时候。请注意,它推断类型 ()
为 p2
因为这就是 .subscribe
返回这里。现在,回到基于键路径的分配:
class Thing {
var iv: UIImageView! = UIImageView()
func test() {
let im = UIImage()
let kp = \UIImageView.image
self.iv[keyPath: kp] = im
}
}
这不会编译,出现错误
value of optional type 'UIImage?' must be unwrapped to a value of type 'UIImage'
.我不知道为什么 Swift 不能编译这个。如果我们显式转换 im
,它就会编译至 UIImage?
:class Thing {
var iv: UIImageView! = UIImageView()
func test() {
let im = UIImage()
let kp = \UIImageView.image
self.iv[keyPath: kp] = .some(im)
}
}
如果我们更改
iv
的类型,它也会编译至 UIImageView?
并选择分配:class Thing {
var iv: UIImageView? = UIImageView()
func test() {
let im = UIImage()
let kp = \UIImageView.image
self.iv?[keyPath: kp] = im
}
}
但是如果我们只是强制解包隐式解包的 optional 项,它就不会编译:
class Thing {
var iv: UIImageView! = UIImageView()
func test() {
let im = UIImage()
let kp = \UIImageView.image
self.iv![keyPath: kp] = im
}
}
如果我们只是选择赋值,它就不会编译:
class Thing {
var iv: UIImageView! = UIImageView()
func test() {
let im = UIImage()
let kp = \UIImageView.image
self.iv?[keyPath: kp] = im
}
}
我认为这可能是编译器中的错误。
关于swift - Swift ReferenceWritableKeyPath 如何使用 Optional 属性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59637100/