我正在重构一个项目以使用MVVM并使用协议来确保我的视图模型具有一致的结构。对于定义与输入和输出有关的公共属性(基于内部结构),这很好用,但是以相同的方式定义动作被证明是有问题的,因为当前将它们定义为必须引用视图模型属性的闭包。如果我使用与输入和输出属性相同的方法,那么我认为我无法访问包含实例的属性。
示例:
protocol ViewModelType {
associatedtype Input
associatedtype Output
associatedtype Action
}
final class MyViewModel: ViewModelType {
struct Input { var test: String }
struct Output { var result: String }
struct Action {
lazy var createMyAction: Action<String, Void> = { ... closure to generate Action which uses a MyViewModel property }
}
var input: Input
var output: Output
var action: Action
}
如果我做不到,这不会破坏交易,但我很好奇,因为我看不到任何访问父母财产的方法。
答案 0 :(得分:0)
让我们开始注意,createMyAction: Action<String, Void>
引用名为struct
的类型(Action
)就像是泛型一样,但是您没有这样声明,因此不会工作。
要回答有关嵌套struct Action
的问题,可以引用其外部class MyViewModel
-是的,您可以引用static
属性,如下所示:
struct Foo {
struct Bar {
let biz = Foo.buz
}
static let buz = "buz"
}
let foobar = Foo.Bar()
print(foobar.biz)
但是您应该避免使用此类循环引用。而且,我将忽略所有可能能够在非静态属性上实现这种循环引用的丑陋技巧(可能涉及可变的可选类型)。这是一种代码气味。
听起来像您想将Action
声明为函数吗?我本人正在使用此协议:
protocol ViewModelType {
associatedtype Input
associatedtype Output
func transform(input: Input) -> Output
}
最初受SergDort's CleanArchitecture的启发。
您可以从input
准备Observable
(包含UIViewController
)的实例,并调用transform
函数,然后映射转换的Output
(Observables
s)来更新GUI。
因此该代码假定您具有基本的反应性知识。对于Observable
,您可以在RxSwift或ReactiveSwift之间进行选择-是的,它们的名称相似。
如果您对Rx感到满意,它是通过GUI的简单异步更新来实现良好的MVVM体系结构的绝佳方法。在下面的示例中,您将找到记录为here的类型Driver
,但简短的解释是您要用于视图和 >输入视图,因为它更新了GUI线程上的视图,并且保证不会出错。
CleanArchitecture 包含例如PostsViewModel
:
final class PostsViewModel: ViewModelType {
struct Input {
let trigger: Driver<Void>
let createPostTrigger: Driver<Void>
let selection: Driver<IndexPath>
}
struct Output {
let fetching: Driver<Bool>
let posts: Driver<[PostItemViewModel]>
let createPost: Driver<Void>
let selectedPost: Driver<Post>
let error: Driver<Error>
}
private let useCase: PostsUseCase
private let navigator: PostsNavigator
init(useCase: PostsUseCase, navigator: PostsNavigator) {
self.useCase = useCase
self.navigator = navigator
}
func transform(input: Input) -> Output {
let activityIndicator = ActivityIndicator()
let errorTracker = ErrorTracker()
let posts = input.trigger.flatMapLatest {
return self.useCase.posts()
.trackActivity(activityIndicator)
.trackError(errorTracker)
.asDriverOnErrorJustComplete()
.map { $0.map { PostItemViewModel(with: $0) } }
}
let fetching = activityIndicator.asDriver()
let errors = errorTracker.asDriver()
let selectedPost = input.selection
.withLatestFrom(posts) { (indexPath, posts) -> Post in
return posts[indexPath.row].post
}
.do(onNext: navigator.toPost)
let createPost = input.createPostTrigger
.do(onNext: navigator.toCreatePost)
return Output(fetching: fetching,
posts: posts,
createPost: createPost,
selectedPost: selectedPost,
error: errors)
}
}