Is there a difference between global Initialization vs viewDidLoad initialization in Swift
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
Instance of a class and correct initialization
You're going to be stuck until you get the scope of your instance variables corrected. In the gist you linked to, they're declared inside the @implementation { ... }
block of your ViewController
, but here they're declared outside the class completely, which means they're global in scope.
Inside viewDidLoad
, you're redefining scene
and skView
in the scope of that method, hiding the global values while you do your configuration. These local variables then get deallocated at the end of viewDidLoad
, so you've lost access to whatever you've set up there. (They may live on in the view hierarchy, but your global variables won't work.)
You'll want to move those global variables inside the class, and then access them using self.variableName
so you don't mix them up with local variables. Other methods in the class will then also have access to what you've set up in viewDidLoad
:
class GameViewController: UIViewController, UIScrollViewDelegate {
var skView: SKView!
var scene: GameScene!
var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
self.scene = // load the scene
self.skView = SKView(frame: CGRectMake(0.0, 0.0, 1024.0, 768.0))
self.view.addSubview(self.skView)
// etc
}
}
Finally, the "Can't add self as subview" error is happening because of this:
let skView = self.view as SKView
// ...
self.view.addSubview(skView)
how to init() a swift view controller properly?
Initialization depends on a couple of condition.
- If you are using storyboard, you can just remove the
init
and your VC will have default initializer. Make sure either all of your properties have default value or they are optional. - If you are using
xib
or just creating view programmatically you can have custom convenience initializer where you pass some extra data this way.
class MyViewController: ViewController {
var answer: Int
convenience init(answer: Int) {
self.init()
self.answer = answer
// Do other setup
}
}
Swift, confusion with UIBarButtonItem
Well, maybe you are missing how a ViewController works inside.
First, viewDidLoad
is the area were you usually setup or initialize any view or properties of the view. This method is also called only once during the life of the view controller object. This means that self
already exists.
Knowing this, is important to understand what a let
property does, (from Apple)
A constant declaration defines an immutable binding between the constant name and the value of the initializer expression; after the value of a constant is set, it cannot be changed. That said, if a constant is initialized with a class object, the object itself can change, but the binding between the constant name and the object it refers to can’t.
Even though the upper area is where you declare variables and constants, is usually meant for simple initialization, it's an area for just telling the VC that there is an object that you want to work with and will have a class global scope, but the rest of functionality will be added when the view hierarchy gets loaded (means that the object does not depends of self, for example, when adding target to a button, you are referring to a method inside of self)....this variables or constants are called Stored Properties
In its simplest form, a stored property is a constant or variable that is stored as part of an instance of a particular class or structure. Stored properties can be either variable stored properties (introduced by the var keyword) or constant stored properties (introduced by the let keyword).
And finally, you have a lazy stored property that maybe can be applied for what you want:
A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the lazy modifier before its declaration.
Solution: create a lazy var stored property or add his properties inside ViewDidLoad (when self already exists)
lazy private var doneButtonItem : UIBarButtonItem = {
[unowned self] in
return UIBarButtonItem(title: "Next", style:UIBarButtonItemStyle.Plain, target: self, action: #selector(onClickNext(button:)))
}()
OR
let rightBarButton: UIBarButtonItem?
override func viewDidLoad() {
super.viewDidLoad()
rightBarButton = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(onClickNext(button:)))
}
Global 'let' declaration requires an initializer expression
You need a variable if you're not sure what it is so that later you can change it:
private var database: Database?
There's no point in defining a constant if you're not going to actually define it as something.
iOS function to be called once (when application is initialized)
Use this one:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
It should be in your appdelegate class.
Hope it helps
Related Topics
Problem with Swiftui and Foreach on Xcode Playground
How to Create Nested Dictionary Elements in Swift
Swift "Print" Doesn't Appear in Stdout But 3Rd Party C Library Logs Do When Running in Docker on Ecs
Public Default Init in Protocol
Uitableviewcell Subclass Wrong Image in Cell or Old Image Bug
Swift - Connect Delegate to Custom Xib Cell
How to Get a Full List of Running Processes
How to Provide Default Implementation of an Objective-C Protocol in a Swift Protocol Extension
Drawing at Cocoa with Swift Creates an Error
Urlsession Datatask Method Returns 0 Bytes of Data
Why Swift Disallows Weak Reference for Non-Optional Type
How to Prevent SQL Injections with User-Search-Terms in Vapor 4 (Fluent 4)
Dynamic Links Firebase Function Not Being Called at All Xcode 12
[Core Data]: Threw While Encoding a Value. with Userinfo of (Null)