Swift покращуємо performSegueWithIdentifier або зручний роутер зі сторибордами

Рідкісний розробник під iOS і OS X не використовував сторіборди і ще менше програмістів не передавали дані між екранами.
Всі ми знаємо метод performSegueWithIdentifier і труднощі роботи з ним.

Почавши проект на Swift в один момент мені стало прикро: «Чому ми повинні строго-типизированном мовою використовувати обгортку для передачі даних?»
Через пару хвилин сформувалося бачення вирішення і незабаром реалізація.

Довго думав, чи варто писати про це, оскільки матеріал вкрай невеликий, але ці 50 рядків можуть дуже сильно допомогти

menuController?.performSegueWithIdentifier(changeItemIdentifier, sender: nil) { segue in
let controller = segue.destinationViewController as! ChangeMenuItemController
controller.viewModel.sourceMenuItem = item
}


Таким чином, легко використовувати сторіборди і роутери разом. Усунути розростання prepareForSegue і збільшити контекст читання коду. В той же час це не змушує відмовлятися від prepareForSegue при зручних ситуаціях.

Розберемося як це працює? Не претендую на унікальність, але мені здається підхід цікавим.


1) Створюємо метод аналогічний performSegueWithIdentifier, але з додатковим параметром closure (configurate)
2) Зберігаємо configurate
3) Викликаємо метод performSegueWithIdentifier
4) У момент виклику prepareForSegue залишилося тільки викликати configurate

Можна написати клас, проте набагато продуктивніше написати категорію.
Тонким моментом є збереження configurate і виклик його з prepareForSegue (який важливо не зламати своїми діями).
Для збереження будемо використовувати асоціативні посилання. Замість prepareForSegue буде викликатися наш метод, звідки буде викликатися configurate і вже потім викликатися оригінальний метод.

Верхнеуровневый код
typealias ConfiguratePerformSegue = (UIStoryboardSegue) -> ()
func performSegueWithIdentifier(identifier: String, sender: AnyObject?, configurate: ConfiguratePerformSegue?) {
swizzlingPrepareForSegue()
configuratePerformSegue = configurate
performSegueWithIdentifier(identifier, sender: sender)
}



Реалізація дуже невелика, і простіше докласти код під спойлер
Реалізація
extension UIViewController {
struct AssociatedKey {
static var ClosurePrepareForSegueKey = "ClosurePrepareForSegueKey"
static var token: dispatch_once_t = 0
}

typealias ConfiguratePerformSegue = (UIStoryboardSegue) -> ()
func performSegueWithIdentifier(identifier: String, sender: AnyObject?, configurate: ConfiguratePerformSegue?) {
swizzlingPrepareForSegue()
configuratePerformSegue = configurate
performSegueWithIdentifier(identifier, sender: sender)
}

private func swizzlingPrepareForSegue() {
dispatch_once(&AssociatedKey.token) {
let originalSelector = #selector(UIViewController.prepareForSegue(_:sender:))
let swizzledSelector = #selector(UIViewController.closurePrepareForSegue(_:sender:))

let instanceClass = UIViewController.self
let originalMethod = class_getInstanceMethod(instanceClass, originalSelector)
let swizzledMethod = class_getInstanceMethod(instanceClass, swizzledSelector)

let didAddMethod = class_addMethod(instanceClass, originalSelector,
method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

if didAddMethod {
class_replaceMethod(instanceClass, swizzledSelector,
method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
}

func closurePrepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let closure = configuratePerformSegue {
closure(segue)
}
closurePrepareForSegue(segue, sender: sender)
configuratePerformSegue = nil
}

var configuratePerformSegue: ConfiguratePerformSegue? {
get {
let box = objc_getAssociatedObject(self, &AssociatedKey.ClosurePrepareForSegueKey) as? Box
return box?.value as? ConfiguratePerformSegue
}
set {
objc_setAssociatedObject(self, &AssociatedKey.ClosurePrepareForSegueKey, Box(newValue), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
}



На цьому все. Це дуже маленький хак, який працює і на objc, і на Swift.
Це не тільки не порушує написаний вами код раніше, але і дозволяє комбінувати обидва підходу або ж повністю перейти на конфігурування з блоків.
Так само це дозволяє винести залежність переходів між екранами в спеціальний Router клас і використовувати разом з сторибордами
Джерело: Хабрахабр

0 коментарів

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