Is there a difference between global Initialization vs viewDidLoad initialization in Swift

887 views Asked by At

If I have a class and initialize a variable like so:

class TestClass: UIViewController {
    var thisInt: Int = 10
}

is that any different than initializing like so:

class TestClass: UIViewController {
    var thisInt: Int!

    override func viewDidLoad() {
        super.viewDidLoad()
        thisInt = 10
    }
}

I suppose my main questions lie in when the global initialization takes place, and is there a time when one would get called more than the other would be called with normal iOS Programming (not doing anything drastically against native development). I do understand that doing it in viewDidLoad restricts me to using weak or an optional, but I'm more concerned about any other differences.

2

There are 2 answers

5
Cristik On BEST ANSWER

You can find out easily by adding a "computed" property to your view controller:

class TestClass: UIViewController {
    let a = {()->Int in print("global initialization"); return 10 }()
}

and adding a

print("didFinishLaunching")

in app's delegate didFinishLaunchingWithOptions method.

The order you'll get is

global initialization
didFinishLaunching

which means global initializers run before the app lifecycle begins.

Now, to go even further, you could add a main.swift file with the following contents

print("Before UIApplicationMain")

UIApplicationMain(CommandLine.argc, unsafeBitCast(CommandLine.unsafeArgv, to: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>.self), nil, NSStringFromClass(AppDelegate.self))

and remove (or comment) the @UIApplicationMain decoration from your AppDelegate class. This will instruct the compiler to use the code in main.swift for the application initialization instead of the default behavior provided by the decorator in discussion (although we're providing a custom almost identical implementation).

What you'll get in this 2nd approach is

Before UIApplicationMain
global initialization
didFinishLaunching

which means that the instance property code is executed when the storyboard is loaded.

Now, for more insight, let's try to find out the differences between static an instance variables. For this we'll add a test class:

class PropertyInitializationTest {
    static let staticProp = {()->String in print("static initialization of InitializationTest"); return "" }()
    let instanceProp = {()->String in print("instance initialization of InitializationTest"); return "" }()

    init() {
        print("In initializer")
    }
}

, and update AppDelegate's did finish launching:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        print("didFinishLaunching")
        print("before instantiating InitializationTest")
        _ = PropertyInitializationTest()
        print("after instantiating InitializationTest")
        // Override point for customization after application launch.
        return true
    }

The output we get is:

Before UIApplicationMain
global initialization
didFinishLaunching
before instantiating InitializationTest
instance initialization of InitializationTest
In initializer
after instantiating InitializationTest

, which confirms the fact that instance properties get set when the class is instantiated, and before any initializer code runs.

But wait! What about the static property? There are no traces to indicate that it was initialized at all. Looks like static properties are lazy by definition, and get initialized only when accessed the first time.

Updating the app did finish launching code confirms this.

print("didFinishLaunching")
print("before instantiating InitializationTest")
_ = PropertyInitializationTest()
print("after instantiating InitializationTest")
_ = PropertyInitializationTest.staticProp
print("after instantiating InitializationTest")

gives the following output:

Before UIApplicationMain
global initialization
didFinishLaunching
before instantiating InitializationTest
instance initialization of InitializationTest
In initializer
after instantiating InitializationTest
static initialization of InitializationTest
after instantiating InitializationTest

To conclude:

  • instance properties receive the compile-time values (if set) before the class initializer runs, thus before any code from that class gets executed
  • static properties receive their values only when firstly accessed, they are lazy by nature
2
Andrej On

Yes, there are differences. Although both of your examples would technically work and your first snippet is the most common.

I'll show you one example, when you actually need to implement it as described in your second snippet:

class ImageDisplayingViewController: UIViewController {
    @IBOutlet weak var thumbImageView: UIImageView!

    var choosenImageTag: Int!
    var choosenImage: UIImage!

    override func viewDidLoad() {
        super.viewDidLoad()
        thumbImageView.tag = imageTag
        thumbImageView.image = image
    }
}

Let's say you have a view controller where you choose an image and then navigate to the next view controller to display this image (and a tag, so to keep Int as in your example). You would pass that image and the tag in the prepare(for segue:, sender:) by calling:

destinationViewController.choosenImage = choosenImage
destinationViewController.choosenImageTag = 10

Then the ImageDisplayingViewController will actually load the image in the viewDidLoad() method, where you're sure that your outlets have been initialised.

If you were about to just try and load the image directly in the prepare(for segue:, sender:) method you would get a crash, as outlets were not initialised yet.

destinationViewController.thumbImageView.image = choosenImage  // crash
destinationViewController.thumbImageView.tag = 10