【发布时间】:2023-03-31 07:24:01
【问题描述】:
存在的基础:在阅读之前,了解您不能通过 keypath \UIImageView.image 将 UIImage 分配给图像视图插座的 image 属性会有所帮助。这是属性:
@IBOutlet weak var iv: UIImageView!
现在,这会编译吗?
let im = UIImage()
let kp = \UIImageView.image
self.iv[keyPath:kp] = im // error
不!
可选类型“UIImage?”的值必须解包为“UIImage”类型的值
好的,现在我们已经为实际用例做好了准备。
我真正想了解的是Combine 框架.assign 订阅者如何在幕后工作。为了进行实验,我尝试使用自己的 Assign 对象。在我的示例中,我的发布者管道生成一个 UIImage 对象,我将它分配给 UIImageView 属性 self.iv 的 image 属性。
如果我们使用.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 是一个可选的 UIImage,而我的发布者会生成一个简单明了的 UIImage。
我试图通过在键路径中展开 Optional 来解决这个问题:
let assign = Subscribers.Assign(object: self.iv, keyPath: \UIImageView.image!)
pub.subscribe(assign)
酷,编译。但它在运行时崩溃,大概是因为图像视图的图像最初是nil。
现在我可以通过在我的管道中添加一个map 来解决所有这些问题,该管道将 UIImage 包装在一个 Optional 中,以便所有类型都正确匹配。但我的问题是,这真正是如何工作的?我的意思是,为什么我不必在我使用.assign 的第一个代码中这样做?为什么我可以在那里指定 .image 键路径?关于关键路径如何与可选属性一起工作似乎有些技巧,但我不知道它是什么。
在 Martin R 的一些输入之后,我意识到如果我们明确键入 pub 以生成 UIImage?,我们将获得与添加将 UIImage 包装在 Optional 中的 map 相同的效果。所以这编译和工作
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 是如何工作的。看来它能够push 将管道类型的可选性up 放入.receive 运算符中。但我不明白这怎么可能。
【问题讨论】:
-
只是好奇:如果
self.iv.image为零,你的第一个代码会发生什么? -
@MartinR 它工作正常。 (在现实生活中,我会继续
.storeAnyCancellable,正如您所期望的那样,代码下载并显示图像就好了。)这就是我感到困惑的原因;直到我试图通过提供我自己的分配订阅者、订阅它并将其推广到 AnyCancellable(未显示)来梳理.assign在幕后所做的事情时,我才发现存在“问题”。 -
@MartinR 我会将其添加到 q 中。
-
到目前为止我还没有使用 Combine 的经验,所以我不“期待”任何东西 :) – 但我注意到
let pub = ...的推断类型是Publishers.ReceiveOn<Publishers.CompactMap<Publishers.ReplaceError<Publishers.Map<URLSession.DataTaskPublisher, Data>>, UIImage>, DispatchQueue>。如果您明确地注释它,但将UIImage替换为UIImage?,那么错误就会消失。这似乎是编译器在第一个示例中从.assign(to: \.image, on: self.iv)推断的内容,因为self.iv.image是可选的。 -
如果您将
compactMap替换为map,该错误也会消失——这有意义吗?我不确定。
标签: swift optional combine swift-keypath