Почему Storyboard — не лучшая идея

в 18:54, , рубрики: iOS, storyboard, swift, разработка под iOS

Итак, вы начали новый проект в Xcode. Первое, что я предлагаю сделать, это удалить Main.storyboard. Почему? Потому что от него исходит много проблем.

Чем мне не угодил Storyboard

Не спорю, Storyboard — очень удобная вещь. Все контроллеры расположены в одном месте, причем, все они соединены переходами (segues). Можно сказать, что приложение будто находится у вас на ладонях. И это замечательно, ведь не всегда удается запомнить, к какому контроллеру мы перейдем, если нажмем на очередную кнопку или ячейку.
Но, это все? Есть ли еще какие-нибудь преимущества? На самом деле нет. Зато приходится мириться с многими неприятными вещами.

Неприятность первая

Все контроллеры расположены в одном месте

А так ли это хорошо? Возможно, если в приложении их не так много. Но что обычно происходит при увеличении их количества? А вот что:

Storygetti

Выглядит не очень, да и поддерживать такое вряд ли кому-то захочется.
(Ах да, еще оно тормозит)

Неприятность вторая

Да, держать все контроллеры в одном месте это ужасно. Может ли быть что-нибудь хуже? Может. Например, если над таким большим приложением работает не один человек. Если двое разработчика занимаются интерфейсом, который находится в одном Storyboard’е, в разных ветках, то как им потом объединить эти изменения? Ответ простой — никак. Скорее всего, кому-то придется слить себе чужие изменения и сделать свою работу заново.
И чем чаще будут происходить такие моменты, тем горячее вам будет сидеть на стуле.

Неприятность третья

А вот и самая большая проблема для меня на текущий момент: передача зависимостей. Приведу небольшой пример:
Если следовать MVVM, то у каждого контроллера должна быть ViewModel, причем породить ее должна родительская ViewModel. Вот как это может выглядеть при использовании Storyboard:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == Segues.MoviePreview {
        let controller = segue.destinationViewController as? MoviePreviewController
        controller?.movieViewModel = viewModel.movieViewModel()
    }
}

При этом, MoviePreviewController выглядит как-то так:

class MoviePreviewController: UIViewController {

    var movieViewModel: MovieViewModel?
    
    // ...
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        if let movieTitle = movieViewModel?.title {
            doSomethingWithMovieTitle(movieTitle)
        } else {
            // Report absence of title
        }
    }

}

Проблема в том, что movieViewModel у нас Optional, хотя мы знаем, что там обязательно должно быть значение. Это выливается в совсем ненужные проверки при попытке извлечь нужные нам данные.

Некоторые скажут, что это совсем не проблема, ведь мы можем насильно развернуть значение с помощью специального оператора
!
или, еще лучше, объявить
movieViewModel
как
MovieViewModel!
, и они будут правы. Такая возможность есть, но она приносит с собой еще большую проблему: крэши в рантайме.

Например, если в методе
prepareForSegue(_:sender:)
вместо
segue.identifier == Segues.MoviePreview
написать
segue.identifier == Segues.HahaYouHaveAProblem
, то мы можем быть уверены, что в скором времени приложение упадет.

Что можно сделать

Первые две неприятности решаются без каких-либо особых усилий. Если приложение должно работать на iOS 9+, то можно без зазрения совести воспользоваться Storyboard Reference и разделить один большой Storyboard на множество (в разумных пределах) мелких.

Правда, для iOS 7–8+ решение не будет таким же бесшовным и придется дописывать руками что-то наподобии такого:

let storyboard = UIStoryboard(name: Storyboards.SomeStory, bundle: nil)
let viewController = storyboard.instantiateInitialViewController()
 
if let viewController = viewController {
    presentViewController(viewController, animated: true, completion: nil)
}

И опять же, это ведет к потенциальным крэшам при инициализации Storyboard’a с неправильным именем.
А вот чтобы решить третью проблему, придется, ни много ни мало, вернуться к старым добрым .xib’ам.
И, в таком случае,
MoviePreviewController
может выглядеть так:

class MoviePreviewController: UIViewController {

    var movieViewModel: MovieViewModel?
    
    // ...
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        if let movieTitle = movieViewModel?.title {
            doSomethingWithMovieTitle(movieTitle)
        } else {
            // Report absence of title
        }
    }

}

И его инициализация:

@IBAction func buttonTapped(sender: AnyObject) {
    let controller = MoviePreviewController(movieViewModel: viewModel.movieViewModel())
    presentViewController(controller, animated: true, completion: nil)
}

Теряем ли мы что-нибудь, отказываясь от Storyboard в пользу .xib’ов? Ничего, кроме вышеперечисленного.

Ссылки:
Storyboard from hell
Изначальная статья

Автор: devnikor

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js