Чому Storyboard — не найкраща ідея

Отже, ви почали новий проект в 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 8+, то можна без докорів совісті скористатися Storyboard Reference і розділити один великий Storyboard на безліч (в розумних межах) дрібних.

Правда, для iOS 7+ рішення не буде таким же безшовним і доведеться дописувати руками щось на зразок такого:
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
Початкова стаття

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.