添加编程视图而不是情节提要后出现意外的 nil

Unexpected nil after adding programmatic view instead of storyboard

我有一个以 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 { [=10=].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 { [=11=].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
    }
}

'broken'协调器完全一样,只是我换了

 let viewController = PostsListViewController.instance()

let viewController = FeedViewController()

我完全不知道为什么会这样。各个点的打印语句和断点在任何值上都没有出现 nil。

如果共享示例应用程序会更容易,请告诉我,因为我知道代码片段可能不是最明显的。

tableView.refreshControlnil。您正在尝试强制访问 nil refreshControl。

Refreshing 属性 是 Enabled,因为您的情节提要中的 UITableViewController 有效。在编程版本中,refreshControl 不会自动创建。

refreshControl 属性 的默认值为 nil。您需要在 UIRefreshControl 存在之前将其实例化并分配给 self.refreshControl

当您使用情节提要创建视图并启用它时,系统会在幕后为您处理。您将需要以编程方式自行实现。