定义单独的 ViewModel 加工 Model,并把适合展示的数据输出给 View
软件设计有个重要的原则——封装变化,分析前面两个方案的局限性,我们可以明确的知道,“数据加工”是一个比较容易变化的点,而 View 和 Model 相对来说要稳定一些。那我们就把“数据加工”这个逻辑单独封装起来,把变化的部分和不变的部分隔离,这样 View 和 Model 的复用性就都提高了。
protocol ViewModelType {
var avatarURL: NSURL { get }
var followers: AttributedTitle { get }
var repositories: AttributedTitle { get }
var following: AttributedTitle { get }
var nickname: String { get }
var bio: String { get }
}
struct ProfileHeaderViewModel: ViewModelType {
// MARK: Output
let avatarURL: NSURL
let followers: AttributedTitle
let repositories: AttributedTitle
let following: AttributedTitle
let nickname: String
let bio: String
init(input: Profile) {
avatarURL = NSURL(string: input.avatar) ?? defaultImageURL
nickname = input.login
bio = input.bio ?? defailtProfileItem
followers = (title: "\(input.followers) \n\(attributingFollowers)", attributingTitle: attributingFollowers)
repositories = (title: "\(input.repos) \n\(attributingRepos)", attributingTitle: attributingRepos)
following = (title: "\(input.following) \n\(attributingFollowing)", attributingTitle: attributingFollowing)
}
}
这个 ViewModel 以一个 Model 为输入,以一些可以直接被 View 使用的数据为输出。然后我们把它注入到 View 中即可,注入的方式无所谓,无论是作为初始化参数,抑或是作为属性或者方法参数等等,都可以,只要它是能被外部注入的,而不是由 View 自己生成的即可。譬如把它作为属性:
var viewModel: ViewModelType! {
didSet {
nicknameLabel.text = viewModel.nickname
configAvatar(viewModel.avatarURL)
bioLabel.text = viewModel.bio
configButtons(viewModel.followers, viewModel.repositories, viewModel.following)
}
}
RxSwift + MVVM
protocol ViewModelType {
var avatarURL: Driver<NSURL> { get }
var followers: Driver<AttributedTitle> { get }
var repositories: Driver<AttributedTitle> { get }
var following: Driver<AttributedTitle> { get }
var nickname: Driver<String> { get }
var bio: Driver<String> { get }
}
struct ProfileHeaderViewModel: ViewModelType {
// MARK: Output
let avatarURL: Driver<NSURL>
let followers: Driver<AttributedTitle>
let repositories: Driver<AttributedTitle>
let following: Driver<AttributedTitle>
let nickname: Driver<String>
let bio: Driver<String>
init(input: Profile) {
let profileDriver = Driver.of(input)
avatarURL = profileDriver
.map { $0.avatar }
.map { NSURL(string: $0) ?? defaultImageURL }
followers = profileDriver
.map { (title: "\($0.followers) \n\(attributingFollowers)", attributingTitle: attributingFollowers) }
repositories = profileDriver
.map { (title: "\($0.repos) \n\(attributingRepos)", attributingTitle: attributingRepos) }
following = profileDriver
.map { (title: "\($0.following) \n\(attributingFollowing)", attributingTitle: attributingFollowing) }
nickname = profileDriver
.map { $0.login }
bio = profileDriver
.map { $0.bio ?? defailtProfileItem }
}
}
class ProfileHeader: UIView {
// ...
private let bag = DisposeBag()
var viewModel: ViewModelType! {
didSet {
viewModel.nickname
.drive(nicknameLabel.rx_text)
.addDisposableTo(bag)
viewModel.avatarURL
.drive(onNext: configAvatar)
.addDisposableTo(bag)
viewModel.bio
.drive(bioLabel.rx_text)
.addDisposableTo(bag)
Driver.zip(viewModel.followers, viewModel.repositories, viewModel.following) { ($0.0, $0.1, $0.2) }
.drive(onNext: configButtons)
.addDisposableTo(bag)
}
}
// ...
}
网友评论