Выполнение кода до инициализации первого контроллера представления (приложение на основе раскадровки)

Моему приложению необходимо выполнить некоторые задачи очистки - удалять старые данные, хранящиеся локально, - каждый раз при запуске, прежде чем отобразится начальный контроллер представления.

Это связано с тем, что контроллер начального представления загружает существующие данные при инициализации и отображает их в табличном представлении.

Я установил несколько точек останова и обнаружил, что инициализатор контроллера начального представления (init?(coder aDecoder: NSCoder)) выполняется до application(_:didFinishLaunchingWithOptions) - даже до application(_:**will**FinishLaunchingWithOptions), точнее.

Я мог бы поместить код очистки в самый верх инициализатора контроллера представления, но я хочу отделить логику очистки от любого конкретного экрана. Этот контроллер представления может в конечном итоге не быть начальным контроллером представления однажды.

Переопределение делегата приложения init() метод и помещая мой код очистки там делает работу, но...

Вопрос:

Разве нет лучшего / более элегантного способа?


Примечание. Для справки порядок выполнения соответствующих методов следующий:

  1. AppDelegate.init ()
  2. ViewController.init ()
  3. AppDelegate.application (_: willFinishLaunchingWithOptions:)
  4. AppDelegate.application (_: didFinishLaunchingWithOptions:)
  5. ViewController.viewDidLoad ()

Разъяснение:

Задача очистки не длинная и не должна выполняться асинхронно: напротив, я бы предпочел, чтобы мой первоначальный контроллер представления даже не создавался до его завершения (я знаю, что система убьет мое приложение, если потребуется долго для запуска. Однако, это просто удаление некоторых локальных файлов, ничего страшного.)

Я задаю этот вопрос главным образом потому, что до появления раскадровок код запуска приложения выглядел так:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];

    // INITIAL VIEW CONTROLLER GETS INSTANTIATED HERE 
    // (NOT BEFORE):
    MyViewController* controller = [[MyViewController alloc] init];

    self.window.rootViewController = controller;

    [self.window makeKeyAndVisible];
    return YES;
}

... но теперь, поскольку загрузка основной раскадровки происходит за кулисами, контроллер представления инициализируется до запуска любого из методов делегата приложения.

Я не ищу решение, которое требует добавления пользовательского кода в мой начальный контроллер представления.

4 ответа

Решение

Я не уверен, что есть более элегантный способ, но, безусловно, есть и другие способы...

Я бы предпочел, чтобы мой первоначальный контроллер представления даже не создавался, пока не завершится

Это не является проблемой. Все, что вам нужно сделать, это удалить UIMainStoryboardFile или же NSMainNibFile ключ от Info.plist который говорит UIApplicationMain какой пользовательский интерфейс должен быть загружен. Впоследствии вы запускаете свою "логику очистки" в AppDelegate и как только вы закончите, вы сами инициируете пользовательский интерфейс, как вы уже показали в примере.

Альтернативным решением будет создание подкласса UIApplicationMain и запустите очистку до загрузки пользовательского интерфейса.

Пожалуйста, смотрите App Life Cycle ниже:

Жизненный цикл приложения для iOS

  1. Вы можете добавить UIImageView в свой начальный ViewController, который будет содержать заставку вашего приложения.

  2. В viewDidLoad()... установите свойство imageview.hidden False... выполняете ли вы операцию очистки и по завершении задачи очистки задайте для свойства imageview.hidden значение TRUE.

Этот пользователь не будет знать, какую работу вы делаете, и этот подход используется во многих признанных приложениях.

В качестве альтернативы, если вы хотите, чтобы часть кода выполнялась перед всем, вы можете переопределитьAppDelegateхinit()так:

      @main
class AppDelegate: UIResponder, UIApplicationDelegate {
    override init() {
        DoSomethingBeforeEverthing()       // You code goes here
        super.init()
    }
...
}

Несколько моментов, которые нужно помнить:

  1. Возможно, вы захотите проверить, что разрешено/можно сделать здесь, т.е. до инициализации делегата приложения.
  2. Также более чистым способом было бы создание подкласса AppDelegate и добавление нового метода делегата, который вы вызываете из init, скажемapplicationWillInitializeи если нужноapplicationDidInitialize

Я столкнулся с очень похожей ситуацией

где мне нужно было запустить код, который был готов только после didFinishLaunchingNotification

Я придумал этот шаблон, который также работает с восстановлением состояния

      var finishInitToken: Any?

required init?(coder: NSCoder) {
    
    super.init(coder: coder)
    
    finishInitToken = NotificationCenter.default.addObserver(forName: UIApplication.didFinishLaunchingNotification, object: nil, queue: .main) { [weak self] (_) in
        self?.finishInitToken = nil
        self?.finishInit()
    }
    
}

func finishInit() {
    ...
}

override func decodeRestorableState(with coder: NSCoder) {
    
    // This is very important, so the finishInit() that is intended for "clean start"
    // is not called
    if let t = finishInitToken {
        NotificationCenter.default.removeObserver(t)
    }
    finishInitToken = nil
    
}

alt вы можете сделать уведомление для willFinishLaunchingWithOptions

Другие вопросы по тегам