Итак, вы начали новый проект в 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
ссылка на оригинал статьи http://habrahabr.ru/post/275241/
Добавить комментарий