Aug 1, 2014
Files and Initialization
By now, most of you have written a small Swift app or experimented in the playground. You may even have experienced an error after you copied code from a playground into another file and wondered, “What is actually going on? What is the difference between a playground file, and other Swift source files?” This post will explain how Swift deals with the files in your project, and how global data is initialized.
Files in an App
A Swift app is composed of any number of files, each with the functions, classes, and other declarations that make up the app. Most Swift files in your app are order-independent, meaning you can use a type before it is defined, and can even import modules at the bottom of the file (although that is not recommended Swift style.)
However, top-level code is not allowed in most of your Swift source files. For clarity, any executable statement not written within a function body, within a class, or otherwise encapsulated is considered top-level. We have this rule because if top-level code were allowed in all your files, it would be hard to determine where to start the program.
Playgrounds, REPL, and Top-Level Code
You may be wondering why the code below works perfectly in a playground. This example isn’t encapsulated in anything, so it must be top-level code:
The above single-line program works — with no additional code at all — because playground files do support the execution of top-level code. Code within a playground file is order-dependent, run in top-down lexical order. For example, you can’t use a type before you define it. Of course, Swift playground files can also define functions, classes, and any other legal Swift code, but they don’t need to. This makes it easy to learn the Swift language or try a new API without writing a lot of code to get started.
In addition to playgrounds, top-level code can also be run in the REPL (Read-Eval-Print-Loop) or when launching Swift files as scripts. To use Swift for scripting, you can use shebang-style launching by starting your Swift file with “#!/usr/bin/xcrun swift” or type “xcrun swift myFile.swift” within Terminal.
Application Entry Points and “main.swift”
You’ll notice that earlier we said top-level code isn’t allowed in most of your app’s source files. The exception is a special file named “main.swift”, which behaves much like a playground file, but is built with your app’s source code. The “main.swift” file can contain top-level code, and the order-dependent rules apply as well. In effect, the first line of code to run in “main.swift” is implicitly defined as the main entrypoint for the program. This allows the minimal Swift program to be a single line — as long as that line is in “main.swift”.
In Xcode, Mac templates default to including a “main.swift” file, but for iOS apps the default for new iOS project templates is to add @UIApplicationMain to a regular Swift file. This causes the compiler to synthesize a main entry point for your iOS app, and eliminates the need for a “main.swift” file.
Alternatively, you can link in an implementation of main written in Objective-C, common when incrementally migrating projects from Objective-C to Swift.
Given how Swift determines where to start executing an app, how should global variables work? In the following line of code, when should the initializer run?
var someGlobal = foo()
In a single-file program, code is executed top-down, similar to the behavior of variables within a function. Pretty simple. The answer for complex apps is less obvious, and we considered three different options:
- Restrict initializers of global variables to be simple constant expressions, as C does.
- Allow any initializer, run as a static constructor at app load time, as C++ does.
- Initialize lazily, run the initializer for a global the first time it is referenced, similar to Java.
The first approach was ruled out because Swift doesn’t need constant expressions like C does. In Swift, constants are generally implemented as (inlined) function calls. And there are good reasons to use complex initializers, e.g. to set up singletons or allocate a dictionary.
The second approach was ruled out because it is bad for the performance of large systems, as all of the initializers in all the files must run before the application starts up. This is also unpredictable, as the order of initialization in different files is unspecified.
Swift uses the third approach, which is the best of all worlds: it allows custom initializers, startup time in Swift scales cleanly with no global initializers to slow it down, and the order of execution is completely predictable.
The lazy initializer for a global variable (also for static members of structs and enums) is run the first time that global is accessed, and is launched as dispatch_once to make sure that the initialization is atomic. This enables a cool way to use dispatch_once in your code: just declare a global variable with an initializer and mark it private.
Swift is designed to make it easy to experiment in a playground or to quickly build a script. A complete program can be a single line of code. Of course, Swift was also designed to scale to the most complex apps you can dream up. With “main.swift” you can take complete control over initialization or you can let @UIApplicationMain do the startup work for you on iOS.
All Blog Posts