5

My app needs to perform some cleanup tasks -delete old data stored locally- every time on startup, before the initial view controller is displayed.

This is because that initial view controller loads the existing data when initialized, and displays it inside a table view.

I set up several breakpoints and found out that the initial view controller's initializer (init?(coder aDecoder: NSCoder)) is run before application(_:didFinishLaunchingWithOptions) - even before application(_:**will**FinishLaunchingWithOptions), to be precise.

I could place the cleanup code at the very top of the view controller's initializer, but I want to decouple the cleanup logic from any particular screen. That view controller may end up not being the initial view controller one day.

Overriding the app delegate's init() method and putting my cleanup code there does get the job done, but...

Question:

Isn't there a better / more elegant way?


Note: For reference, the execution order of the relevant methods is as follows:

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

Clarification:

The cleanup task is not lengthy and does not need to run asynchronously: on the contrary, I'd prefer if my initial view controller isn't even instantiated until it completes (I am aware that the system will kill my app if it takes to long to launch. However, it's just deleting some local files, no big deal.).

I am asking this question mainly because, before the days of storyboards, app launch code looked like this:

- (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;
}

...but now, because main storyboard loading happens behind the scenes, the view controller is initialized before any of the app delegate methods run.

I am not looking for a solution that requires to add custom code to my initial view controller.

Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189

4 Answers4

6

I am not sure if there is more elegant way but there are definitely some other ways...

I'd prefer if my initial view controller isn't even instantiated until it completes

This is not a problem. All you have to do is to delete a UIMainStoryboardFile or NSMainNibFile key from the Info.plist which tells the UIApplicationMain what UI should be loaded. Subsequently you run your "cleanup logic" in the AppDelegate and once you are done you initiate the UI yourself as you already shown in the example.

Alternative solution would be to create a subclass of UIApplicationMain and run the cleanup in there before the UI is loaded.

Please see App Life Cycle below:

iOS App Life Cycle

0101
  • 2,697
  • 4
  • 26
  • 34
0
  1. You can add a UIImageView on your initial ViewController which will contain the splash image of your app.

  2. In viewDidLoad()... make the imageview.hidden property False... do you cleanup operation and on completion of cleanup task make the imageview.hidden property TRUE.

By this user will be unaware of what job you are doing and this approach is used in many recognized app.

Vizllx
  • 9,135
  • 1
  • 41
  • 79
  • Your question was "Isn't there a better / more elegant way?", I showed you one of the way,rest is your choice, you can divide the task in different models and define it. As you only mentioned, view controller loads the existing data when initialized so if you want to control that, this is an alternative way of doing it. – Vizllx Jan 28 '16 at 07:57
0

I faced a very simmilar situation

where i needed to run code, which was only ready after didFinishLaunchingNotification

i came up with this pattern which also works with state restoration

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 you can make a notif for willFinishLaunchingWithOptions

Peter Lapisu
  • 19,915
  • 16
  • 123
  • 179
0

Alternatively, if you want a piece of code to be run before everything, you can override the AppDelegate's init() like this:

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

Few points to remember:

  1. You might want to check, what's allowed/can be done here, i.e. before app delegate is initialized
  2. Also cleaner way would be, subclass the AppDelegate, and add new delegate method that you call from init, say applicationWillInitialize and if needed applicationDidInitialize
ImShrey
  • 380
  • 4
  • 12