MutableProperty:执行值访问方法

时间:2018-03-29 11:45:03

标签: ios swift reactive-swift

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

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

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

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

  1. ViewControllerA想要访问userAvatar
  2. 这是第一次访问userAvatar然后发出API请求
  3. ViewControllerA侦听userAvatar信号
  4. ViewControllerA暂时显示占位符
  5. ViewControllerB想要访问userAvatar
  6. ViewControllerB侦听userAvatar信号
  7. ViewControllerB暂时显示占位符
  8. 完成userAvatar的API请求,然后发送viewcontrollers观察到的信号
  9. viewcontrollers正在使用新图像刷新UIImageView
  10. 这是我的实际代码:

    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概念的新手。 随意批评,我正在努力提高自己

    提前感谢您的建议。

1 个答案:

答案 0 :(得分:1)

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

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

  2. 如果image当前正在下载时SignalProducerimage完成下载后,会.value(image)后面跟.completed

  3. 如果image尚未下载且在SignalProducer启动时当前未下载:启动image下载,image下载完成发出.value(image)后跟.completed

  4. 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)的调用都是显式停止信号以进行内存管理的示例。