Swift 3 has brought us some interesting access control changes. The differences between open and public or private and fileprivate take some getting used to. Luckily unless you are writing a framework the new rules are simple. Here is what you need to know.
Note: Swift 4 has changed the scope of private access reducing the need to use fileprivate for extension access. See Swift 4 access levels for details.
The Five Access Levels of Swift 3
Swift 3 has five access levels that control from which source file or module you can access something. In order from most open to most restricted:
-
openyou can accessopenclasses and class members from any source file in the defining module or any module that imports that module. You can subclass anopenclass or override anopenclass member both within their defining module and any module that imports that module. -
publicallows the same access asopen- any source file in any module - but has more restrictive subclassing and overriding. You can only subclass apublicclass within the same module. Apublicclass member can only be overriden by subclasses in the same module. This is important if you are writing a framework. If you want a user of that framework to be able to subclass a class or override a method you must make itopen. -
internalallows use from any source file in the defining module but not from outside that module. This is generally the default access level. -
fileprivateallows use only within the defining source file. -
privateallows use only from the enclosing declaration. This has changed in Swift 4 - see Swift 4 access levels.
Note that Objective-C classes and methods are now imported as open.
Don’t Panic
It is worth remembering that an application is a module and that internal access is the default. This means application code you write is accessible from all source files in the application by default.
Unless you are writing a framework you probably only need to think about access levels when you want to restrict access to a single source file using fileprivate or private.
Using fileprivate and private
Let’s look at some examples where we might want to use fileprivate and private. Suppose I have a view controller that has a property which I do not want accessed outside of the source file. In Swift 2 I would declare it as private and move on:
class RootViewController: UIViewController {
private var someFlag = false
}
Unfortunately, using Swift 3, if I now try to access this property from a class extension, in the same source file, I hit a problem:
extension RootViewController: MyGreatDelegate {
func doSomething {
if someFlag {
// do the thing
}
}
}
// Use of unresolved identifier 'someFlag'
The problem is that the private access level restricts access to the property to the enclosing class declaration. The extension is not allowed access even though it is in the same source file. The solution is to switch the access level to fileprivate:
class RootViewController: UIViewController {
fileprivate var someFlag = false
}
Another example using private in extensions for methods that you do not want accessed outside of the extension. For example, helper methods for a delegate:
extension RootViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
handle(text: textField.text)
textField.resignFirstResponder()
return true
}
// Not accessible outside of this extension
private func handle(text: String?) {
// do something
}
}
Other Hints and Tips
Getters and Setters
You can give a setter lower access than its getter to limit the read-write access.
class MyDataSource {
// property is read-only outside of this class definition
private(set) var someFlag = false
// ...
}
By default the property getter and setter in this example would have internal access. Making the setter have a private access level means we can only set it within the class definition. We cannot set it from another source file or from a class extension in the same source file. If we wanted to be able to set the property from an extension we would need to give the setter fileprivate access:
fileprivate(set) var someFlag = false
Unit Test Targets
A unit test target is its own module so does not have access to any types or properties in the application module that are by default internal. You give your tests internal access by importing with the @testable attribute:
import XCTest
@testable import MyDataSource
class MyDataSourceTests: XCTestCase {
// func testSomething() {...}
}
Final - Preventing Overrides
The final keyword is not an access level but you can add it to any of the access levels (apart from open) to prevent subclassing or overriding. There is a potential performance improvement from this as the compiler can avoid dynamic dispatch though you can allow the compiler to infer this if you use Whole Module Optimization.
Further Reading
- SE-0025 Scoped Access Level describes the
public,internal,fileprivateandprivateaccess levels. - SE-0117 Allow distinguishing between public access and public overridability creates the new
openaccess level on classes.