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:
-
open
you can accessopen
classes and class members from any source file in the defining module or any module that imports that module. You can subclass anopen
class or override anopen
class member both within their defining module and any module that imports that module. -
public
allows the same access asopen
- any source file in any module - but has more restrictive subclassing and overriding. You can only subclass apublic
class within the same module. Apublic
class 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
. -
internal
allows use from any source file in the defining module but not from outside that module. This is generally the default access level. -
fileprivate
allows use only within the defining source file. -
private
allows 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
,fileprivate
andprivate
access levels. - SE-0117 Allow distinguishing between public access and public overridability creates the new
open
access level on classes.