Ios 使用ReactiveCocoa(4.2.1)和Swift异步加载图像
我是第一次在Swift中使用ReactiveCocoa的初学者。我正在构建一个显示电影列表的应用程序,我正在使用MVVM模式。我的ViewModel如下所示:Ios 使用ReactiveCocoa(4.2.1)和Swift异步加载图像,ios,swift,mvvm,reactive-cocoa,reactive-cocoa-4,Ios,Swift,Mvvm,Reactive Cocoa,Reactive Cocoa 4,我是第一次在Swift中使用ReactiveCocoa的初学者。我正在构建一个显示电影列表的应用程序,我正在使用MVVM模式。我的ViewModel如下所示: class HomeViewModel { let title:MutableProperty<String> = MutableProperty("") let description:MutableProperty<String> = MutableProperty("") var i
class HomeViewModel {
let title:MutableProperty<String> = MutableProperty("")
let description:MutableProperty<String> = MutableProperty("")
var image:MutableProperty<UIImage?> = MutableProperty(nil)
private var movie:Movie
init (withMovie movie:Movie) {
self.movie = movie
title.value = movie.headline
description.value = movie.description
Alamofire.request(.GET, movie.pictureURL)
.responseImage { response in
if let image = response.result.value {
print("image downloaded: \(image)")
self.image.value = image
}
}
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MovieCell", forIndexPath: indexPath) as! MovieCell
let movie:Movie = movieList[indexPath.row]
let vm = HomeViewModel(withMovie: movie)
// fill cell with data
vm.title.producer.startWithNext { (newValue) in
cell.titleLabel.text = newValue
}
vm.description.producer.startWithNext { (newValue) in
cell.descriptioLabel.text = newValue
}
vm.image.producer.startWithNext { (newValue) in
if let newValue = newValue {
cell.imageView?.image = newValue as UIImage
}
}
return cell
}
这是活性可可的正确方法吗?我是否需要将标题和描述声明为可变的,还是仅将图像声明为唯一更改的。我想我可以使用绑定,但我不确定如何继续。要使用反应式Cocoa+MVVM模式实现这一点,我首先将配置单元的所有逻辑从其viewmodel移动到单元类本身。然后从viewModel中删除可变属性,它们实际上是不可变的,我们不需要这些信号。对于映像,公开一个信号生成器,它将在调用start时执行网络请求以获取映像,而不是在ViewModel上调用init时隐式获取映像,这给了我们类似的信息
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MovieCell", forIndexPath: indexPath) as! MovieCell
cell.viewModel = self.viewModelForIndexPath(indexPath)
return cell
}
private func viewModelForIndexPath(indexPath: NSIndexPath) -> MovieCellViewModel {
let movie: Movie = movieList[indexPath.row]
return HomeViewModel(movie: movie)
}
然后
class MovieCell: UITableViewCell
@IBOutlet weak var titleLabel: UILabel
@IBOutlet weak var descriptionLabel: UILabel
@IBOutlet weak var imageView: UIImageView
var viewModel: MovieCellViewModel {
didSet {
self.configureFromViewModel()
}
}
private func configureFromViewModel() {
self.titleLabel.text = viewModel.title
self.descriptionLabel.text = viewModel.description
viewModel.fetchImageSignal()
.takeUntil(self.prepareForReuseSignal()) //stop fetching if cell gets reused
.startWithNext { [weak self] image in
self?.imageView.image = image
}
}
//this could also go in a UITableViewCell extension if you want to use it other places
private func prepareForReuseSignal() -> Signal<(), NoError> {
return Signal { observer in
self.rac_prepareForReuseSignal // reactivecocoa builtin function
.toSignalProducer() // obj-c RACSignal -> swift SignalProducer
.map { _ in () } // AnyObject? -> Void
.flatMapError { _ in .empty } // NSError -> NoError
.start(observer)
}
}
}
在ViewModel中
struct HomeViewModel {
private var movie: Movie
var title: String {
return movie.headline
}
var description: String {
return movie.description
}
func fetchImageSignal() -> SignalProducer<UIImage, NSError> {
return SignalProducer { observer, disposable in
Alamofire.request(.GET, movie.pictureURL)
.responseImage { response in
if let image = response.result.value {
print("image downloaded: \(image)")
observer.sendNext(image) //send the fetched image on the signal
observer.sendCompleted()
} else {
observer.sendFailed( NSError(domain: "", code: 0, userInfo: .None)) //send your error
}
}
}
}
为了使用Cocoa+MVVM模式实现这一点,我首先将配置单元的所有逻辑从其viewmodel移动到单元类本身。然后从viewModel中删除可变属性,它们实际上是不可变的,我们不需要这些信号。对于映像,公开一个信号生成器,它将在调用start时执行网络请求以获取映像,而不是在ViewModel上调用init时隐式获取映像,这给了我们类似的信息
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MovieCell", forIndexPath: indexPath) as! MovieCell
cell.viewModel = self.viewModelForIndexPath(indexPath)
return cell
}
private func viewModelForIndexPath(indexPath: NSIndexPath) -> MovieCellViewModel {
let movie: Movie = movieList[indexPath.row]
return HomeViewModel(movie: movie)
}
然后
class MovieCell: UITableViewCell
@IBOutlet weak var titleLabel: UILabel
@IBOutlet weak var descriptionLabel: UILabel
@IBOutlet weak var imageView: UIImageView
var viewModel: MovieCellViewModel {
didSet {
self.configureFromViewModel()
}
}
private func configureFromViewModel() {
self.titleLabel.text = viewModel.title
self.descriptionLabel.text = viewModel.description
viewModel.fetchImageSignal()
.takeUntil(self.prepareForReuseSignal()) //stop fetching if cell gets reused
.startWithNext { [weak self] image in
self?.imageView.image = image
}
}
//this could also go in a UITableViewCell extension if you want to use it other places
private func prepareForReuseSignal() -> Signal<(), NoError> {
return Signal { observer in
self.rac_prepareForReuseSignal // reactivecocoa builtin function
.toSignalProducer() // obj-c RACSignal -> swift SignalProducer
.map { _ in () } // AnyObject? -> Void
.flatMapError { _ in .empty } // NSError -> NoError
.start(observer)
}
}
}
在ViewModel中
struct HomeViewModel {
private var movie: Movie
var title: String {
return movie.headline
}
var description: String {
return movie.description
}
func fetchImageSignal() -> SignalProducer<UIImage, NSError> {
return SignalProducer { observer, disposable in
Alamofire.request(.GET, movie.pictureURL)
.responseImage { response in
if let image = response.result.value {
print("image downloaded: \(image)")
observer.sendNext(image) //send the fetched image on the signal
observer.sendCompleted()
} else {
observer.sendFailed( NSError(domain: "", code: 0, userInfo: .None)) //send your error
}
}
}
}