Ссылка на свойства содержащего класса при использовании внутренних структур в swift - PullRequest
0 голосов
/ 05 апреля 2019

Я выполняю рефакторинг проекта для использования 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 Ответ

1 голос
/ 05 апреля 2019

Ответ на ваш вопрос

Давайте начнем с примечания, что 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 .

Вы можете подготовить экземпляр input (содержащий Observable s) из UIViewController и вызвать функцию transform, а затем отобразить Output преобразования (будучи Observables s), чтобы обновить GUI.

Итак, в этом коде предполагается, что вы обладаете базовыми знаниями по реактиву . Что касается Observable s, вы можете выбрать между RxSwift или ReactiveSwift - да, их имена похожи.

Если вы знакомы с Rx, это отличный способ создания красивой архитектуры MVVM с простыми асинхронными обновлениями графического интерфейса. В приведенном ниже примере вы найдете тип Driver, который задокументирован здесь , но краткое объяснение состоит в том, что вы хотите использовать для ввода из представлений и вход в представлений, так как он обновляет представления в потоке графического интерфейса, и это гарантированно не выдает ошибку.

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)
    }
}
...