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.

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
}
  • 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.