Swift Access Control
Modules are just a bundle of code that is used in other Modules through the import statement. In simpler terms, a Module is just a folder in which you hold your swift files.
Access Control handles the restrictions of your code. By defining the access of your classes, functions, properties, enumerations, protocols, you have the power to use these stuff at places you want to, and hide it from where you don’t need it, in your XCode Project.
[quote]Whenever we write entities in this tutorial, it’ll refer to these: classes, functions, properties, enumerations, protocols.[/quote]
Swift Access Levels
The following are the major levels that provide you with access control. They’re ordered from least restricted to the highest.
- open: Least restrictive. Entities can be accessed from any file in the current module or different module files. Classes/Members with an open modifier can be inherited/overridden anywhere in the current module or different modules.
- public: Less restrictive. Entities can be accessed in the same or different modules. Classes/Members with a public modifier cannot be inherited/overridden anywhere in modules other than the current module.
- internal: Default access level. If no access level is specified, this is the default access level. It allows entities to be used within any source file in their defined module but not outside that module. The same rule applies to inheritance too.
- fileprivate: This access level restricts the use of entities to the current file only. Subclassing a fileprivate class would require you to specify the subclass access level to fileprivate or a more restrictive one.
- private: Most restrictive. Entities with this access level can be accessed only in the enclosed scope. Also, any extension to that declaration has access too.
The following flow aptly shows the access levels for each
Swift Access Control
The syntax of classes and properties looks like this
open class AnOpenClass { open func AnOpenFunction() {} } public class APublicClass { public var APublicVariable = 0 } internal class ExplicitInternalClass { internal let ExplicitInternalConstant = 0 } fileprivate class AFilePrivateClass { fileprivate func AFilePrivateFunction() {} } private class APrivateClass { private func APrivateFunction() {} } class ImplicitInternalClass { func ImplicitInternalFunction(){} }
Swift Access Control – Properties
A Property – var, let cannot have more access than the type it is assigned to
class AClass { //Your code } public var a = AClass()
/*Error: Variable cannot be declared public because its type 'AClass' uses an internal type */ open class BClass { //Your code } public var b = BClass() private class CClass { //Your code } private var c = CClass()
- class AClass is internal, so it’ll fail to compile when is public(more access) and a private class has to have a private property defined.
Swift Access Control Getter / Setter Methods
The getters and setters on a property would get the same access level as defined on the property. We can set a different access level on the setter too. But, the setter cannot have a higher access level than the property. A fileprivate getter and setter property
import Foundation class AClass { var length: Double = 5.0 let breadth: Double = 1.1 fileprivate var area: Double { get { return length*breadth } set(newArea) { length = newArea/breadth } } } var aObject = AClass() print(aObject.area)
- The getter will always have the same access level as the property.
- The following is a public getter and a private setter.
import Foundation class AClass { var length: Double = 5.0 let breadth: Double = 1.1 public private(set) var area: Double { get { return length*breadth } set(newArea) { length = newArea/breadth } } } var aObject = AClass() print(aObject.area)
The following would fail since the setter has more access than the property and its getter
import Foundation class AClass { var length: Double = 5.0 let breadth: Double = 1.1 private fileprivate(set) var area: Double { //Error: private property cannot have a fileprivate setter get { return length*breadth } set(newArea) { length = newArea/breadth } } } var aObject = AClass() print(aObject.area) //Error 'area' is inaccessible due to 'private' protection level
Swift Access Control – Classes
The access level defined for a class may or may not be applied to the members and functions. A class with public access means that its members will have internal access by default unless stated otherwise. The following image shows two classes. The private members of one class cannot be accessed in the other.
Access control with Subclasses
- Rule 1: A subclass cannot have a higher access level than it’s superclass. The following example won’t compile.
class AClass { //Internal class } public class BClass: AClass { //Error: Class cannot be declared public because its superclass is internal //public subclass not allowed. }
- Rule 2: Overridden methods can have a higher access level than their superclass counterparts.
class AClass { //Internal class fileprivate func heyMethod() { print("heyMethod") } internal func helloMethod() { print("helloMethod") } } fileprivate class BClass : AClass { override public func heyMethod() { print("Overridden heyMethod") } override fileprivate func helloMethod() { print("Overridden helloMethod") } }
- Rule 3: A private function cannot be overridden. An overridden function cannot be private.
class AClass { //Internal class fileprivate func heyMethod() { print("heyMethod") } internal func helloMethod() { print("helloMethod") } } fileprivate class BClass : AClass { override public func heyMethod() { print("Overridden heyMethod") } override private func helloMethod() { //Error: Overriding instance method must be as accessible as its enclosing type print("Overridden helloMethod") } }
Swift Access Control – Tuples
The access level of a swift tuple would be the level of the most restrictive element among the elements present.
import Foundation fileprivate class AClass {
var str: String var number: Int
init(s: String, n: Int) { str = s number = n } } private class BClass {
var str: String var number: Int
init(s: String, n: Int) { str = s number = n } } class GClass { //Error: Property must be declared fileprivate because its type uses a private type
var tuple: (AClass,BClass) = (AClass(s: "Balu",n: 101), BClass(s: "Balututorial.com",n: 102)) }
In the above code, the access level of the tuple must be set to private, the more restrictive of the two-level
import Foundation fileprivate class AClass { var str: String var number: Int init(s: String, n: Int) { str = s number = n } } private class BClass { var str: String var number: Int init(s: String, n: Int) { str = s number = n } } class GClass { //Error: Property must be declared fileprivate because its type uses a private type private var tuple: (AClass,BClass) = (AClass(s: "Balu",n: 101), BClass(s: "Balututorial.com",n: 102)) func printData() { print("str:\(tuple.0.str) Number:\(tuple.0.number)") print("str:\(tuple.1.str) Number:\(tuple.1.number)") } } let xObj = GClass() xObj.printData()
Swift Access Control with Function Types
Functions in swift, like Tuples, have the access level equal to the most restrictive level among the parameters and the return type. But you have to specify the access level of the functions explicitly too. If the specified access level does not match the calculated one, it’ll throw a compiler error. The below code looks fine but it would not compile
import Foundation fileprivate class AClass { var str : String var number: Int init(s: String, n: Int) { str = s number = n } } class BClass { func printData(s: String,y: Int) -> AClass { //Error: Method must be declared fileprivate because its result uses a fileprivate type print("\(s) and \(y)") return AClass(s: s, n: y) } }
The return type printData() is AClass which is a fileprivate class. Hence you must make the function fileprivate.
Swift Access Control with init function
Swift Initializers must have the same or lesser access level than its parameters defined. The following code defines an initializer in class G that would fail since it has a higher access level.
import Foundation fileprivate class AClass { var str: String var number: Int init(s: String, n: Int) { str = s number = n } } private class BClass { var str: String var number: Int init(s: String, n: Int) { str = s number = n } } class GClass { private var tuple : (AClass,BClass) init(f1: AClass, f2: BClass) { //Error: Initializer must be declared fileprivate because its parameter uses a fileprivate type tuple = (AClass(s: "Balu",n: 101), BClass(s: "Balututorial.com",n: 102)) } }
We can set the initializer above to either of the fileprivate or private. Required initializers follow the same access rule as initializers along with the rule that the access level should be the same as that of the enclosed class. So just keeping the fileprivate level on the required init would give the following error.
So either make the parameters as the same type of the class OR vice-versa. Here we’ll do the later and make the class as fileprivate. Continuing with our previous code, using required init would make the class GClass look like this:
import Foundation fileprivate class AClass { var str : String var number: Int init(s: String, n: Int) { str = s number = n } } private class BClass { var str : String var number: Int init(s: String, n: Int) { str = s number = n } } fileprivate class GClass { private var tuple : (AClass,BClass) required fileprivate init(f1: AClass, f2: BClass) { tuple = (AClass(s: "Balu",n: 101), BClass(s: "Balututorial.com",n: 102)) } }
Default Memberwise Initializers for Structure Types
For structure initialiser, we must set the initializer to the following level based on the members
- If any of the initializer members are private or fileprivate the init should be fileprivate
- Else the init by default is internal.
import Foundation fileprivate class AClass { var str : String var number: Int init(s: String, n: Int) { str = s number = n } } private class BClass { var str : String var number: Int init(s: String, n: Int) { str = s number = n } } struct StructureExample { private var tuple: (AClass,BClass) fileprivate init(f1: AClass, f2: BClass) { tuple = (AClass(s: "Balu",n: 101), BClass(s: "Balututorial.com",n: 102)) } }
Swift Access Control Example – Protocols
Protocol access level must be set at the time of defining that protocol. The protocol functions/requirements must have the same access level defined. Just like Subclassing, if a protocol inherits from another protocol, it cannot have a higher access level than the inherited one.
private protocol ProtocolA { //Your code } private protocol ProtocolB : ProtocolA { //Your code }
You cannot set ProtocolB to any higher level such as fileprivate.
Protocol Conformance
- A class can conform to a protocol with a lower access level.
- If a class is public and a protocol is private then the type conformance is private.
- It always the lower one that’s considered.
- If the protocol methods are implemented in the class/structure/enum, they must be set to an access level that is equal to the protocol access level or higher than that.
Swift Access Control – Enumerations
The access level for an enumeration would be applied across all its cases
private enum Color{ case red case blue }
- red and blue have the same access type private
- The access level of the raw values should be greater than or equal to the enum access level.
Swift Access Control – Extensions
Any type members added in an extension must have the same default access level as type members declared in the original type being extended. To change the access level to a different one from the class, you just need to add the access modifier on the extension and it’ll change the default access levels of the extensions members too unless you set the levels on members explicitly.
- You cannot declare a private or fileprivate class with an extension of public or internal or open
- We can access private members from a class/struct in their extensions.
- We can access private members from one extension in another.
Swift Access Control – Generics and Type Aliases
- The access level for a generic type or generic function is the minimum of the access level of the generic type or function itself and the access level of any of its members/return type.
- A type alias can have an access level less than or equal to the access level of the type it aliases.
This is the same as subclassing. Type alias cannot use a higher access level than it’s typing.