【问题标题】:Referring to properties of containing class when using internal structs in swift在 swift 中使用内部结构时引用包含类的属性
【发布时间】:2019-08-27 10:42:52
【问题描述】:

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

如果我不能这样做,这不是一个交易破坏者,但我很好奇,因为我看不到任何访问父属性的方法。

【问题讨论】:

    标签: swift mvvm protocols


    【解决方案1】:

    回答您的问题

    让我们首先注意 createMyAction: Action&lt;String, Void&gt; 引用名为 Action 的类型 (struct),就好像它是一个泛型一样,但您没有声明它,因此它不起作用。

    要回答嵌套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

    您可以从UIViewController 准备input 的实例(包含Observables)并调用transform 函数,然后映射变换的Output(即Observabless)以更新图形用户界面。

    因此,此代码假定您具有基本的响应式知识。至于Observables,您可以在RxSwiftReactiveSwift 之间进行选择——是的,它们的名字相似。

    如果您对 Rx 感到满意,那么它是通过简单的 GUI 异步更新实现良好 MVVM 架构的绝佳方式。在下面的示例中,您将找到类型为 Driver,它记录在 here 中,但简短的解释是您希望将其用于 input from 视图和 input to 视图,因为它会更新 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)
        }
    }
    

    【讨论】:

    • 我通过创建一个结构(称为 Actions)来“破解”它,该结构包含可公开访问的 getter,用于定义我所追求的 RxSwift 操作的私有惰性变量。它有效,但我确实感到不舒服,这可能是我的潜意识警告我有代码气味。我认为问题在于我试图将这些操作本身视为一个单独的实体,而不是作为 VM 的输出(当然,它们是)。感谢您的指点 - 我将重构,看看它是否会消除我刚刚遇到的另一个问题......
    • 如果您需要更多输入,请告诉我。你可以看看这个用于加密货币 Zilliqa 的 iOS 钱包应用程序,名为 Zhip,这是我编写的,它是开源的。 Zhip 在其 ViewModels 中使用这个transform(input) -&gt; Output 模式。 github.com/OpenZesame/Zhip
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-21
    • 1970-01-01
    • 2018-10-20
    • 1970-01-01
    • 2020-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多