快速使用内部结构时引用包含类的属性

时间:2019-04-05 13:50:03

标签: swift mvvm protocols

我正在重构一个项目以使用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
}

如果我做不到,这不会破坏交易,但我很好奇,因为我看不到任何访问父母财产的方法。

1 个答案:

答案 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)

但是您应该避免使用此类循环引用。而且,我将忽略所有可能能够在非静态属性上实现这种循环引用的丑陋技巧(可能涉及可变的可选类型)。这是一种代码气味。

MVVM建议

听起来像您想将Action声明为函数吗?我本人正在使用此协议:

protocol ViewModelType {
    associatedtype Input
    associatedtype Output
    func transform(input: Input) -> Output
}

最初受SergDort's CleanArchitecture的启发。

您可以从input准备Observable(包含UIViewController)的实例,并调用transform函数,然后映射转换的OutputObservables s)来更新GUI。

因此该代码假定您具有基本的反应性知识。对于Observable,您可以在RxSwiftReactiveSwift之间进行选择-是的,它们的名称相似。

如果您对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)
    }
}