Итак, вы начали новый проект в Xcode. Первое, что я предлагаю сделать, это удалить Main.storyboard. Почему? Потому что от него исходит много проблем.
Чем мне не угодил Storyboard
Не спорю, Storyboard — очень удобная вещь. Все контроллеры расположены в одном месте, причем, все они соединены переходами (segues). Можно сказать, что приложение будто находится у вас на ладонях. И это замечательно, ведь не всегда удается запомнить, к какому контроллеру мы перейдем, если нажмем на очередную кнопку или ячейку.
Но, это все? Есть ли еще какие-нибудь преимущества? На самом деле нет. Зато приходится мириться с многими неприятными вещами.
Неприятность первая
Все контроллеры расположены в одном месте
А так ли это хорошо? Возможно, если в приложении их не так много. Но что обычно происходит при увеличении их количества? А вот что:
Выглядит не очень, да и поддерживать такое вряд ли кому-то захочется.
(Ах да, еще оно тормозит)
Неприятность вторая
Да, держать все контроллеры в одном месте это ужасно. Может ли быть что-нибудь хуже? Может. Например, если над таким большим приложением работает не один человек. Если двое разработчика занимаются интерфейсом, который находится в одном 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