Access Control in Swift
In this article, we are going to get familiar with access control keywords. I believe that the most of the new developers does not know about the concept of access control well enough. When I first learnt about software development with Java, I admit that I also didn’t know enough about access control or encapsulation. Although it does not seem as an important concept at first, it can be a determination point if your code is clean or not.
Disclaimer: This article will mostly be an interpretation of the Access Control documentation of the official swift website. I will try to simplify the parts that I see important and disregard the less important parts.
The main idea of the access control is that every class, struct, enum, function or property (which I will call them entities from now on) should be visible only to the entities that they somehow interact with.
Official Swift website describes access control restrictions as follows;
Access control restricts access to parts of your code from code in other source files and modules. This feature enables you to hide the implementation details of your code, and to specify a preferred interface through which that code can be accessed and used.
Some access levels are in the scope of the whole module, and some are in the source file the code lives in. I believe that, in order to make everything easier to grasp, we need to know what is called a module or a source file.
Module vs Source File
A module can be called to a single application or a framework that can be distributed as a single component. This distribution might be an application that is published as a single unit or a framework that can be included into another framework or application using the “import” keyword.
A source file is a single swift file that can contain one or more Swift entities (classes, structs, enums, properties etc.)
To be more practical; when we start the Xcode and create an “App”, we create a single module containing bunch of swift files from the start that can be seen on file hierarchy from the left side of the screen. Each of the file is called a Source File, and the whole application can be called a Module.
After clearing the meanings of a module and a source file , we can proceed to getting familiar with access levels.
Access Levels are the keywords used for determining the access control type of an entity. These levels can be relative to either the source file or the whole module that contains the source files.
I will be ordering the access level in respect to their restriction level. The first one is the least restrictive and the last one is the most.
Open access is the least restrictive access level allowing the entities to be used from all modules. The entities having the open keyword in front of them can be called or used from any other module like an application importing your module as a library or framework.
Public access is almost identical to open access with a subtle difference about inheritance. The official swift website describes the difference as follows;
Open access applies only to classes and class members, and it differs from public access by allowing code outside the module to subclass and override, as discussed below in Subclassing. Marking a class as open explicitly indicates that you’ve considered the impact of code from other modules using that class as a superclass, and that you’ve designed your class’s code accordingly.
Internal access lets the entities be used from any source file in the same module that they live in. This means that an internal entity can be called or used from any other source file – for example from a different controller – that resides in the same module.
This access level is the default level in Swift. If you don’t put any keyword in front of your entities, the swift compiler acknowledges that entity as internal.
Fileprivate access level is used for making an entity reachable from inside of a source file. If there are more than one class, structs or enums in the source file, an entity marked as fileprivate can be used from all of them as long as they are in the same source file.
Private access is the most restrictive access level in Swift. In this access level, entity which has the private keyword, can only be accessed from the same enclosing declaration or its extension. For example, a fileprivate function can only be used in the same class, struct or enum that lives in or their extensions.
When you designing a framework or a library, use internal (default access level) in order to hide the implementation of your entities from the code importing your framework, use private or fileprivate for more encapsulation. You need to mark an entity as open or public only if you want it to become part of your framework’s API.
There are some special cases where the access levels of entities have to respect the access level of the entities associated with them.
Access level of a function has to be equal to the access level of the most restrictive entity that is either its parameter or return type.
If we create a internal function with parameters or return types (like above) with more restrictive access levels, the compiler would give an error since the function’s access level has to comply to the entity having the most restrictive access level.
Enums also has a special case when it comes to access levels. You cannot mark the cases with an access level. Each case gets the enum’s access level automatically. For example if we specify an enum as public, all of the cases would be public as well.
Subclassing follows a similar rule. When you subclass a parent class in the same module or in a different module, the subclass cannot be less restrictive then its parent class (superclass). That means, if we want to inherit an internal class, we would have to declare the subclass as internal, fileprivate or private. We cannot make the subclass a higher access level like public or open.
Overriding sometimes makes access control hard to understand for the subclassing. If both superclass and the subclass in the same module, we could override any class member (method, property, initializer, or subscript) that’s visible in a certain access context. However, if we want to subclass from another module, we could only override a class member marked as open.
When we subclass a class, we can override a more restrictive class member and make it less restrictive. For example, we can override a fileprivate function and make the override function as internal as long as it respect the access level of its class. We can see the example below.
One special case I can talk about is with getters, setters and subscripts. Getters, setters and subscripts get the same access level with the entity (constant, variable, property etc.) they belong to.
One useful information with getters and setters, you can set a lower access level to the setter than its corresponding getter in order to restrict the write functionality of the entity. That way, we can make a property (or variable) readable from other classes in respect to its access level, but restrict changing its value to a specific scope. Here is an example;
Lastly, I want to mention how access control of extensions work and to set their access levels. You can extend any class, struct and enum in any context that they are accessible and any entity defined in that extension gets the same access level of the object being extended in the default. If any internal type would be extended, the entities added to the extension would get the access level of internal as well. Similarly, any fileprivate type’s extension would get fileprivate access level as default and any private type’s extension would get private access level as default. One special case for that, if we want to extend a public type, any entity that we add to extension would be internal by default.
Alternatively, if you want to declare an access level to the extension, the entities added to that extension would get that access level by default. However, you can still override the access levels of the entities added to the extension by marking their access levels explicitly.
In my opinion, access control is a basic but extremely fundamental concept of Swift that every iOS developer should know. I hope this article was helpful and easy to understand. Stay healthy.