Неожиданный ноль после добавления программного представления вместо раскадровки

У меня есть приложение, написанное по шаблону MVVM-C с использованием RxSwift

После добавления нового представления программно приложение аварийно завершает работу с

Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

ошибка. Я в полной растерянности, реализация почти такая же, за исключением того факта, что один контроллер представления является раскадровкой, а другой - нет.

Это мой новый ViewController

import UIKit
import RxSwift
import RxCocoa

final class FeedViewController: TableViewController, ViewModelAttaching {

    var viewModel: Attachable<FeedViewModel>!
    var bindings: FeedViewModel.Bindings {
        let viewWillAppear = rx.sentMessage(#selector(UIViewController.viewWillAppear(_:)))
            .mapToVoid()
            .asDriverOnErrorJustComplete()

        let refresh = tableView.refreshControl!.rx
            .controlEvent(.valueChanged)
            .asDriver()

        return FeedViewModel.Bindings(
            fetchTrigger: Driver.merge(viewWillAppear, refresh),
            selection: tableView.rx.itemSelected.asDriver()
        )
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    func bind(viewModel: FeedViewModel) -> FeedViewModel {
        viewModel.posts
            .drive(tableView.rx.items(cellIdentifier: FeedTableViewCell.reuseID, cellType: FeedTableViewCell.self)) { _, viewModel, cell in
                cell.bind(to: viewModel)
            }
            .disposed(by: disposeBag)

        viewModel.fetching
            .drive(tableView.refreshControl!.rx.isRefreshing)
            .disposed(by: disposeBag)

        viewModel.errors
            .delay(0.1)
            .map { $0.localizedDescription }
            .drive(errorAlert)
            .disposed(by: disposeBag)

        return viewModel
    }

}

Это существующий, который работает, но использует раскадровки.

final class PostsListViewController: TableViewController, ViewModelAttaching {

    var viewModel: Attachable<PostsListViewModel>!
    var bindings: PostsListViewModel.Bindings {
        let viewWillAppear = rx.sentMessage(#selector(UIViewController.viewWillAppear(_:)))
            .mapToVoid()
            .asDriverOnErrorJustComplete()
        let refresh = tableView.refreshControl!.rx
            .controlEvent(.valueChanged)
            .asDriver()

        return PostsListViewModel.Bindings(
            fetchTrigger: Driver.merge(viewWillAppear, refresh),
            selection: tableView.rx.itemSelected.asDriver()
        )
    }

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        setupView()
    }

    // MARK: - View Methods

    private func setupView() {
        title = "Posts"
    }

    func bind(viewModel: PostsListViewModel) -> PostsListViewModel {
        viewModel.posts
            .drive(tableView.rx.items(cellIdentifier: PostTableViewCell.reuseID, cellType: PostTableViewCell.self)) { _, viewModel, cell in
                cell.bind(to: viewModel)
            }
            .disposed(by: disposeBag)

        viewModel.fetching
            .drive(tableView.refreshControl!.rx.isRefreshing)
            .disposed(by: disposeBag)

        viewModel.errors
            .delay(0.1)
            .map { $0.localizedDescription }
            .drive(errorAlert)
            .disposed(by: disposeBag)

        return viewModel
    }

}

Они в основном точно такие же. Исключение выдается в строке let refresh = tableView.refreshControl!.rx.

Рабочий координатор, использующий раскадровку,

import RxSwift

class PostsCoordinator: BaseCoordinator<Void> {
    typealias Dependencies = HasPostService

    private let navigationController: UINavigationController
    private let dependencies: Dependencies

    init(navigationController: UINavigationController, dependencies: Dependencies) {
        self.navigationController = navigationController
        self.dependencies = dependencies
    }

    override func start() -> Observable<Void> {
        let viewController = PostsListViewController.instance()
        navigationController.viewControllers = [viewController]

        let avm: Attachable<PostsListViewModel> = .detached(dependencies)
        let viewModel = viewController.attach(wrapper: avm)

        viewModel.selectedPost
            .drive(onNext: { [weak self] selection in
                self?.showDetailView(with: selection)
            })
            .disposed(by: viewController.disposeBag)

        // View will never be dismissed
        return Observable.never()
    }

    private func showDetailView(with post: Post) {
        let viewController = PostDetailViewController.instance()
        viewController.viewModel = PostDetailViewModel(post: post)
        navigationController.showDetailViewController(viewController, sender: nil)
    }

}

У меня есть расширение, позволяющее мне также создавать его экземпляры.

protocol Reusable {
    static var reuseID: String { get }
}

extension Reusable {
    static var reuseID: String {
        return String(describing: self)
    }
}

// MARK: - View Controller

extension UIViewController: Reusable {
    class func instance() -> Self {
        let storyboard = UIStoryboard(name: reuseID, bundle: nil)
        return storyboard.instantiateViewController()
    }
}

extension UIStoryboard {
    func instantiateViewController<T: UIViewController>() -> T {
        guard let viewController = self.instantiateViewController(withIdentifier: T.reuseID) as? T else {
            fatalError("Unable to instantiate view controller: \(T.self)")
        }
        return viewController
    }
}

«Сломанный» координатор точно такой же, за исключением того, что я поменял местами

 let viewController = PostsListViewController.instance()

для

let viewController = FeedViewController()

Я в полной растерянности, почему это бросает. Операторы печати и точки останова в разных точках не давали нуля ни для каких значений.

Пожалуйста, дайте мне знать, если будет проще поделиться примером приложения, так как я понимаю, что фрагменты кода могут быть не самыми очевидными.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
343
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

tableView.refreshControl есть nil. Вы пытаетесь принудительно получить доступ к nil refreshControl.

Свойство Refreshing — это Enabled для UITableViewController в вашей раскадровке, которая работает. В программной версии refreshControl не создается автоматически.

Спасибо, что нашли время, чтобы прокомментировать, я понимаю, что это так, я не понимаю, почему переход от раскадровки к программному UITableViewController приведет к нулю

Tim J 17.03.2019 19:21

@blukkublukku Обновленный ответ. В раскадровке вполне вероятно, что свойство Refreshing UITableViewController установлено в Enabled. Таким образом, элемент управления создается автоматически.

Daniel 17.03.2019 19:36
Ответ принят как подходящий

Значение по умолчанию свойства refreshControl равно нулю. Вам нужно создать экземпляр и присвоить UIRefreshControlself.refreshControl до того, как он будет создан.

Когда вы создаете свое представление с помощью раскадровки и включаете его, об этом позаботятся за вас за кулисами. Программно вам потребуется реализовать это самостоятельно.

Другие вопросы по теме