If you’ve been developing applications in Swift for any length of time, you’ve grown accustomed to often having one or more lines of text at the top of your file. No, I’m not talking about the Xcode generated commented-out metadata when you make a new Swift file (if you’re like me and don’t particularly care for it either, we’ve got you covered) — I’m talking about our friend the import declaration.
Let’s take a look at Swift import declarations.
We’ve all seen this many times:
import UIKit
But this also works:
import UIKit.UITableViewController
let tvc = UITableViewController()
let vc = UIViewController()
let label = UILabel()
And this…almost works:
import class UIKit.UITableViewController
let tvc = UITableViewController()
let vc = UIViewController() // Error
let label = UILabel() // Error
Why?
In these contexts, UITableVIewController
in our import statement are actually
referring to two different things. Let’s look at the Swift documentation.
import [module]
import [module].[submodule]
import [import kind] [module].[symbol name]
In our first example we’re just importing the UIKit
module as usual — no
funny business going on there. In our second example, we’re actually importing
the entire UITableViewController
submodule, whereas in our last example
we’re explicitly importing only a class that has the symbol name
of UITableViewController
.
If we inspect the submodule UITableViewController
we see it imports the
entire umbrella header for UIKit
:
import Foundation
import UIKit
import _SwiftUIKitOverlayShims
//
// UITableViewController.h
// UIKit
//
// Copyright (c) 2008-2017 Apple Inc. All rights reserved.
//
// Creates a table view with the correct dimensions and autoresizing, setting the datasource and delegate to self.
// In -viewWillAppear:, it reloads the table's data if it's empty. Otherwise, it deselects all rows (with or without animation) if clearsSelectionOnViewWillAppear is YES.
// In -viewDidAppear:, it flashes the table's scroll indicators.
// Implements -setEditing:animated: to toggle the editing state of the table.
@available(iOS 2.0, *)
open class UITableViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {
...
Hence why when we do not specify the import kind while importing
UITableViewController
, we’re actually still just importing the entirety
of UIKit
.
Note that there are multiple permissible import kinds available:
typealias | struct | class | enum | protocol | let | var | func
When?
Although there are limited cases when you may explicitly need to specify the import kind and symbol name, it does help remove excessive symbols you may not need in the particular file you’re working in. As far as performance and binary size goes, any unused symbols are already optimized out of the final binary by the Swift compiler. If there’s no reference to it at compile time then it’s removed, meaning that importing a framework but not using particular parts of it shouldn’t have any negative implications.
Besides decluttering Xcode’s code completion, there is one scenario where it does offer a distinct advantage: modules which both define a particular type of the same name.
Consider two frameworks, FrameworkA
and FrameworkB
. For the sake of
simplicity, they both implement a struct called Foo
, but have different
behavior. What happens if we import both frameworks?
import FrameworkA
import FrameworkB
func example() {
let myFoo = Foo(baz: 3) // Using FrameworkA's implementation
let myFooAgain = Foo(bar: "test") // Using FrameworkB's implementation
myFoo.doThingFromFrameworkA()
myFooAgain.doThingFromFrameworkB()
}
Both local variables are of type Foo
, but come from different modules. This
could be confusing…particularly if we didn’t intend on using FrameworkA’s
implementation of Foo
. What can we do if we only wanted to use a different
part of the framework?
import struct FrameworkA.ThingA
import FrameworkB
func example2() {
let myFoo = Foo(baz: 3) // Error: Now FrameworkA's definition of Foo is not in scope
let myFooAgain = Foo(bar: "test") // Still Using FrameworkB's implementation
let thing = ThingA() // But we still have access to ThingA from FrameworkA!
myFooAgain.doThingFromFrameworkB()
}
Now there’s no longer two Foos
in scope, and we’re explicitly only importing
what we intend to use from FrameworkA
. Nice!
Export an import?
Before we conclude our spotlight on Swift import declarations, browsing the
Swift documentation led me to an interesting declaration attribute that’s
currently not officially released: @_exported
According to the docs,
applying this attribute to an import declaration exports the import module,
submodule, or declaration from the current module. For example, consider if
FrameworkA
had the import declaration @_exported import FrameworkC
. In
our application we could then only import FrameworkA
and still be able
to access FrameworkC
thanks to the @_exported
attribute.
But considering that it’s a private Swift attribute (as denoted by that pesky underscore 😒 ), we’re probably best off not using it for the time being.