我正在使用reactiveswift sdwebimage下载/cache useravatars的API,然后在我的ViewControllers中显示它们。
我有多个要显示用户的viewControllers,然后他们收听其异步加载。
我实现下面描述的流程的最佳方法是什么?
我想在这里创建的流程是:
-
ViewControllerA
想要访问用户avatar - 这是第一次访问用户,然后做一个API请求
-
ViewControllerA
lisgens for UserAvatar信号 -
ViewControllerA
暂时显示占位符 -
ViewControllerB
想要访问用户avatar -
ViewControllerB
聆听用户的信号 -
ViewControllerB
暂时显示占位符 - 用户的API请求已完成,然后发送一个由ViewControllers观察到的信号
- viewControllers用新鲜图像为其
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()
很干净...
我知道我的代码不是那么"反应性",我仍然有反应性概念。随时批评,我正在尝试改善自己
事先感谢您的建议。
处理这种情况的最佳方法是创建一个SignalProducer
:
-
如果启动
SignalProducer
时已经下载了image
:立即发射.value(image)
,其次是.completed
-
如果启动
SignalProducer
时image
当前正在下载:image
下载完成后,EMITS.value(image)
随后是.completed
-
如果尚未下载
image
,并且在启动SignalProducer
时尚未下载:启动image
的启动下载,image
完成下载时,下载了EMITS.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
}
}
要使您的代码更加"反应性",您可以使用eactivevecocoa库将avatarImageSignalProducer
绑定到UI:
reactivecocoa不带BindingTarget
用于UIImageView.image
,因此我们自己写一个扩展名:
import ReactiveCocoa
extension Reactive where Base: UIImageView {
public var image: BindingTarget<UIImage> {
return makeBindingTarget { $0.image = $1 }
}
}
这使我们可以使用ViewController中的eactivecocoa绑定操作员在viewDidLoad
/cellForRowAtIndexPath
/等中清理我们的代码:
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
}
}
考虑记忆&amp;将数据绑定到内存中未引用的UI时的周期性引用(例如,如果我们的用户是一个全局变量,则在VC被交易之后留在内存中,而不是VC的属性(。在这种情况下,我们必须明确停止在交易VC时聆听信号,否则将永远不会释放其内存。上面代码中对.take(until: cell.reactive.prepareForReuse)
和.take(during: self.reactive.lifetime)
的调用都是明确停止信号以进行内存管理目的的示例。