Как внедрить зависимости в раскадровку управляемых UIViewControllers? - PullRequest
0 голосов
/ 19 октября 2018

Привет всем, я пытаюсь протестировать один из ViewControllers моего проекта.Этот класс зависит от другого вспомогательного класса, например:

private let dispatcher: Dispatcher = Dispatcher.sharedInstance
private var loginSync = LoginSync.sharedInstance
private var metadataSync = MetadataSync.sharedInstance

Эти вспомогательные классы используются в жизненном цикле UIViewController, например viewDidLoad или viewWillAppear.В моем тесте я создаю экземпляр класса ViewController с помощью класса UIStoryboard, например:

func testSearchBarAddedIntoNavigationViewForiOS11OrMore() {
    // Given a YourFlow ViewController embedded in a navigation controller
    let mockLoginSync = MockLoginSync()
    let storyboard = UIStoryboard(name: "Main", bundle: nil)

    // Here is too early and view controller is not instantiated yet and I can't assign the mock.
    let vc = storyboard.instantiateViewController(withIdentifier: "YourFlow")
    // Here is too late and viewDidLoad has already been called so assigning the mock at this point is pointless.
    let navigationController = UINavigationController(rootViewController: vc)

    // Assertion code
}

Так что моя проблема в том, что мне нужно иметь возможность имитировать класс LoginSync.При обычных обстоятельствах я использовал бы регулярное внедрение зависимостей, передавая эти помощники в качестве аргументов в конструктор класса.В этом случае я не могу этого сделать, потому что я не управляю жизненным циклом View Controller.Поэтому, как только я создаю его экземпляр, помощники уже используются.

Мой вопрос таков: «Существует ли способ внедрения зависимостей для контроллеров View, жизненный цикл которых мы не можем контролировать, или, по крайней мере, обходной путь для него??

Спасибо.

РЕДАКТИРОВАТЬ: Итак, viewDidLoad был вызван, потому что я использовал IBOutlets в переопределенных методах didSet, а не из-за вызова instantiateViewController. Поэтому я могу переместить этот код и выполнить инъекцию послесоздание экземпляра контроллера представления правильно.

Ответы [ 3 ]

0 голосов
/ 19 октября 2018

Контроллеры представлений в раскадровке всегда инициализируются с использованием init?(coder aDecoder: NSCoder), поэтому при инициализации невозможно установить какие-либо свойства.

Я считаю, что следующее решение - хороший обходной путь…

Вместо использования

let loginSync: LoginSync

Объявить как

private (set) var loginSync: LoginSync!

Объявить

func configure(loginSync: LoginSync) {
    self.loginSync = loginSync
}

Затем

let vc = storyboard.instantiateViewController(withIdentifier: "YourFlow")
vc.configure(loginSync: MockLoginSync())

Вы также можете использовать это в segues…

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    switch segue.destination) {
    case let vc as MyViewController:
        vc.configure(loginSync: MockLoginSync())
    default:
        break
    }
}

Это не идеально, но свойство private (set) гарантирует, что его нельзя изменить из другого класса, а неявное развертывание (!) означает, что вы получите сбой, если онне установлен.

Используйте configure() методы в каждом UIView / UIViewController - как только вы привыкнете к этому шаблону, он станет второй натурой.

0 голосов
/ 19 октября 2018

Вы можете обернуть UIVIewControllerCreation следующим образом:

class func createWith(injection: YourInjection) -> YourViewController {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let vc = storyboard.instantiateViewController(withIdentifier: "YourVCId") as? YourViewController
    vc.injected = injection
    return vc
}

И использовать его следующим образом:

let vc = YourViewController.createWith(<your injection>)

Вот пример:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let vc = RedViewController.createWith(injection: "some")
        navigationController?.pushViewController(vc, animated: true)
    }
}


class RedViewController: UIViewController {
    var injected: String = "" {
        didSet {
            print(#function)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red
        print(#function)
    }

    class func createWith(injection: String) -> RedViewController {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "Red") as! RedViewController
        vc.injected = injection
        return vc
    }
}

Раскадровканастройка:

enter image description here

Печать результатов выполнения кода:

injected
viewDidLoad()

Как вы можете заметить, внедрение происходит до viewDidLoad ()

0 голосов
/ 19 октября 2018

Вам необходимо использовать segues для перехода к контроллеру представления, чтобы вы могли вводить зависимости во время prepareForSegue следующим образом:

override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
    if (segue.identifier == "SomeSegueToYourFlow") {
        if let yourFlowVC = segue.destination as? YourFlowController {
            let mockLoginSync = MockLoginSync()
            yourFlowVC.loginSync = mockLoginSync
        }
    }
}
...