我正在使用ReactiveSwift + SDWebImage下载/缓存API的userAvatars,然后在ViewControllers中显示它们。
我有多个想要显示userAvatar的ViewControllers,然后他们会监听它的异步加载。
我实施下述流程的最佳方式是什么?
我想在这里创建的流程是:
ViewControllerA
想要访问userAvatar ViewControllerA
侦听userAvatar信号ViewControllerA
暂时显示占位符ViewControllerB
想要访问userAvatar ViewControllerB
侦听userAvatar信号ViewControllerB
暂时显示占位符UIImageView
这是我的实际代码:
class ViewControllerA {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ... Cell creation
// type(of: user) == User.self (see class User below)
user.loadAvatarImage()
disposable = user.image.producer
.observe(on: UIScheduler())
.startWithValues { image in
// image is is either a placeholder or the real avatar
cell.userImage.image = image
}
}
}
class ViewControllerB {
override func viewDidLoad() {
super.viewDidLoad()
// type(of: user) == User.self (see class User below)
user.loadAvatarImage()
disposable = user.image.producer
.observe(on: UIScheduler())
.startWithValues { image in
// image is is either a placeholder or the real avatar
headerImageView.image = image
}
}
}
class User: Mappable {
// ... User implementation
let avatarImage = MutableProperty<UIImage?>(nil)
// To call before accessing avatarImage.value
func loadAvatarImage() {
getAvatar { image in
self.avatarImageProperty.value = image
}
}
private func getAvatar(completion: @escaping ((UIImage) -> Void)) {
// ... Async image download
competion(image)
}
}
我没有发现在听取信号之前调用user.loadAvatarImage()
是非常干净的......
我知道我的代码不是那么“反应性”,我仍然是Reactive概念的新手。 随意批评,我正在努力提高自己
提前感谢您的建议。
答案 0 :(得分:1)
处理这种情况的最佳方法是创建一个SignalProducer
:
如果在image
启动时已下载SignalProducer
:立即发出.value(image)
后跟.completed
如果image
当前正在下载时SignalProducer
:image
完成下载后,会.value(image)
后面跟.completed
如果image
尚未下载且在SignalProducer
启动时当前未下载:启动image
下载,image
下载完成发出.value(image)
后跟.completed
ReactiveSwift为我们提供了一个信号生成器的“手动”构造函数,允许我们编写每次启动信号生成器时运行的命令性代码:
private let image = MutableProperty<UIImage?>(.none)
private var imageDownloadStarted = false
public func avatarImageSignalProducer() -> SignalProducer<UIImage, NoError> {
return SignalProducer { observer, lifetime in
//if image download hasn't started, start it now
if (!self.imageDownloadStarted) {
self.imageDownloadStarted = true
self.getAvatar { self.image = $0 }
}
//emit .value(image) followed by .completed when the image has downloaded, or immediately if it has already downloaded
self.image.producer //use our MutableProperty to get a signalproducer for the image download
.skipNil() //dont send the nil value while we wait for image to download
.take(first: 1) //send .completed after image value is sent
.startWithSignal { $0.observe(observer) } //propogate these self.image events to the avatarImageSignalProducer
}
}
为了使您的代码更具“反应性”,您可以使用ReactiveCocoa库将avatarImageSignalProducer
绑定到UI:
ReactiveCocoa没有内置BindingTarget
的{{1}},因此我们自己编写扩展程序:
UIImageView.image
这让我们可以使用ViewControllers中的ReactiveCocoa绑定运算符来清理import ReactiveCocoa
extension Reactive where Base: UIImageView {
public var image: BindingTarget<UIImage> {
return makeBindingTarget { $0.image = $1 }
}
}
/ viewDidLoad
/ etc中的代码,如下所示:
cellForRowAtIndexPath
考虑记忆和记忆也很重要。将数据绑定到视图控制器未在内存中引用的UI时的循环引用(例如,如果我们的用户是在释放VC后保留在内存中的全局变量,而不是VC的属性)。在这种情况下,我们必须在VC被释放时明确停止监听信号,否则永远不会释放其内存。上面代码中对class ViewControllerA {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ... Cell creation
cell.userImage <~ user.avatarImageSignalProducer()
.take(until: cell.reactive.prepareForReuse) //stop listening to signal & free memory when cell is reused before image loads
}
}
class ViewControllerB {
override func viewDidLoad() {
headerImageView.image <~ user.avatarImageSignalProducer()
.take(during: self.reactive.lifetime) //stop listening to signal & free memory when VC is deallocated before image loads
}
}
和.take(until: cell.reactive.prepareForReuse)
的调用都是显式停止信号以进行内存管理的示例。