1

我正在使用 ReactiveSwift + SDWebImage 下载/缓存 API 的 userAvatars,然后将它们显示在我的 ViewControllers 中。

我有多个 ViewControllers 想要显示 userAvatar,然后他们监听它的异步加载。

我实施下述流程的最佳方式是什么?

我想在这里创建的流程是:

  1. ViewControllerA想访问用户头像
  2. 这是第一次访问 userAvatar 然后发出 API 请求
  3. ViewControllerA监听 userAvatar 信号
  4. ViewControllerA临时显示占位符
  5. ViewControllerB想访问用户头像
  6. ViewControllerB监听 userAvatar 信号
  7. ViewControllerB临时显示占位符
  8. userAvatar 的 API 请求完成,然后发送 viewcontrollers 观察到的信号
  9. 视图控制器正在刷新他们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()听信号前打电话很干净……

我知道我的代码不是那么“反应性”,我仍然是反应性概念的新手。随意批评,我正在努力提高自己

提前感谢您的建议。

4

1 回答 1

1

处理这种情况的最佳方法是创建一个SignalProducer

  1. ifimage在启动时已经下载SignalProducer:立即发出.value(image)后跟.completed

  2. if启动时正在下载:image下载完成时,发出后跟SignalProducerimage.value(image).completed

  3. 如果在启动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 没有自带BindingTargetforUIImageView.image内置,所以我们自己写一个扩展:

import ReactiveCocoa

extension Reactive where Base: UIImageView {
  public var image: BindingTarget<UIImage> {
    return makeBindingTarget { $0.image = $1 }
  }
}

这让我们可以使用 ViewControllers 中的 ReactiveCocoa 绑定操作符来清理viewDidLoad/ cellForRowAtIndexPath/etc 中的代码,如下所示:

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
  }
}

在将数据绑定到视图控制器未在内存中引用的 UI 时,考虑内存和循环引用也很重要(例如,如果我们的 User 是在 VC 被释放后保留在内存中的全局变量,而不是VC)。在这种情况下,我们必须在 VC 被释放时显式停止监听信号,否则它的内存将永远不会被释放。上述代码中的调用.take(until: cell.reactive.prepareForReuse)和调用.take(during: self.reactive.lifetime)都是出于内存管理目的显式停止信号的示例。

于 2018-04-02T22:22:06.123 回答