在 swift 中使用内部结构时引用包含 class 的属性

Referring to properties of containing class when using internal structs in swift

我正在重构一个项目以使用 MVVM 并使用协议来确保我的视图模型具有一致的结构。这适用于定义与输入和输出相关的 public 属性(基于内部结构),但以同样的方式定义操作被证明是有问题的,因为目前,它们被定义为必须引用视图模型的闭包特性。如果我使用与输入和输出属性相同的方法,我认为我无法访问包含实例的属性。

示例:

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
}

如果我不能这样做也不是什么大问题,但我很好奇,因为我看不到有任何方法可以访问父级的属性。

回答你的问题

让我们首先注意 createMyAction: Action<String, Void> 引用名为 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)以更新 GUI。

所以这段代码假设你有基本的 Reactive 知识。至于 Observable,您可以在 RxSwift or 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 { [=12=].map { PostItemViewModel(with: [=12=]) } }
        }
        
        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)
    }
}