Swift Access Control
Access control restricts the access levels of your code from other source files and modules.
You can explicitly set the access levels for individual types (classes, structures, enumerations), as well as their properties, functions, initializers, basic types, and subscript indices.
Protocols can also be restricted to a certain scope, including global constants, variables, and functions within the protocol.
Access control is based on modules and source files.
A module refers to a framework or application built and distributed as a single unit. In Swift, a module can import another module using the import
keyword.
A source file is a single source code file, typically belonging to a module, and can contain definitions of multiple classes and functions.
Swift provides four different access levels for entities in your code: public, internal, fileprivate, private.
Access Level | Definition |
---|---|
public | Can access any entity in the source file within their own module and others can access all entities in the source file by importing the module. |
internal | Can access any entity in the source file within their own module, but others cannot access entities in the source file of the module. |
fileprivate | Private within the current source file. |
private | Accessible only within the class or structure it is defined in, and not accessible outside its scope. |
public is the highest access level and private is the lowest.
Syntax
Declare the access level of an entity using the modifiers public, internal, fileprivate, private:
Example
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}
Unless specified otherwise, entities use the default access level internal.
Default Access Level is internal
class SomeInternalClass {} // Access level is internal
let someInternalConstant = 0 // Access level is internal
Function Type Access Permissions
The access level of a function needs to be determined based on the access levels of its parameter types and return types.
The following example defines a global function named someFunction
without explicitly declaring its access level.
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// Function implementation
}
One of the classes, SomeInternalClass
, has an internal access level, and the other, SomePrivateClass
, has a private access level. Therefore, according to the tuple access level principle, the tuple's access level is private (the tuple's access level is the same as the lowest access level type in the tuple).
Because the function's return type has a private access level, you must explicitly declare the function with the private modifier:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// Function implementation
}
Declaring the function as public or internal, or using the default internal access level, would be incorrect because you would not be able to access the private-level return value.
Enumeration Type Access Permissions
The access level of members within an enumeration inherits from the enumeration itself; you cannot declare different access levels for individual members.
Example
For instance, in the following example, the enumeration Student
is explicitly declared as public, so its members Name
and Mark
also have public access levels:
Example
public enum Student {
case Name(String)
case Mark(Int,Int,Int)
}
var studDetails = Student.Name("Swift")
var studMarks = Student.Mark(98,97,95)
switch studMarks {
case .Name(let studName):
print("Student Name: \(studName).")
case .Mark(let Mark1, let Mark2, let Mark3):
print("Student Grades: \(Mark1),\(Mark2),\(Mark3)")
}
The above program execution output is:
Student Grades: 98,97,95
Subclass Access Levels
The access level of a subclass cannot be higher than that of its parent class. For example, if the parent class has an internal access level, the subclass cannot be declared as public.
Example
public class SuperClass {
fileprivate func show() {
print("SuperClass")
}
}
// Access level cannot be higher than superclass public > internal
internal class SubClass: SuperClass {
override internal func show() {
print("SubClass")
}
}
let sup = SuperClass()
sup.show()
let sub = SubClass()
sub.show()
The above program execution output is:
SuperClass
SubClass
Access Levels for Constants, Variables, Properties, and Subscripts
Constants, variables, properties cannot have a higher access level than their types.
For example, if you define a property with a public access level but its type is private, this is not allowed by the compiler.
Similarly, subscripts cannot have a higher access level than their index type or return type.
If the type of a constant, variable, property, or subscript is private, they must be explicitly declared as private:
private var privateInstance = SomePrivateClass()
Getter and Setter Access Levels
The access levels of getters and setters for constants, variables, properties, and subscripts inherit from their respective members' access levels.
The access level of a setter can be lower than that of the corresponding getter, allowing control over the read and write permissions of variables, properties, or subscripts.
Example
class Samplepgm {
fileprivate var counter: Int = 0{
willSet(newTotal){
print("Counter: \(newTotal)")
}
didSet{
if counter > oldValue {
print("New increment: \(counter - oldValue)")
}
}
}
}
let NewCounter = Samplepgm()
NewCounter.counter = 100
NewCounter.counter = 800
The access level of counter is fileprivate, accessible within the file.
The above program execution output is:
Counter: 100
New increment: 100
Counter: 800
New increment: 700
Initializer and Default Initializer Access Levels
Initialization
We can declare the access level for custom initializers, but it cannot be higher than the access level of the class it belongs to. Required initializers are an exception; their access level must be the same as the class's access level.
Like function or method parameters, the access level of initializer parameters cannot be lower than the initializer's access level.
Default Initializer
Swift provides a default parameterless initializer for both structs and classes to assign values to all their properties without specifying any values.
The access level of the default initializer is the same as the access level of the type it belongs to.
Example
Declare the access level before the init()
method in each subclass using the required
keyword.
Example
class classA {
required init() {
var a = 10
print(a)
}
}
class classB: classA {
required init() {
var b = 30
print(b)
}
}
let res = classA()
let show = classB()
The above program execution output is:
10
30
10
Protocol Access Levels
If you want to explicitly declare an access level for a protocol, you need to ensure that the protocol is only used within the declared access level's scope.
If you define a protocol with a public access level, the required functions provided by implementing the protocol will also have a public access level. This is different from other types, such as other types with a public access level, where their members have an internal access level.
Example
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
public class SimpleClass: ExampleProtocol {
public var simpleDescription: String = "A very simple class."
public func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
let a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
print(aDescription)
The above program execution output is:
A very simple class. Now 100% adjusted.
public protocol TcpProtocol {
init(no1: Int)
}
public class MainClass {
var no1: Int // local storage
init(no1: Int) {
self.no1 = no1 // initialization
}
}
class SubClass: MainClass, TcpProtocol {
var no2: Int
init(no1: Int, no2: Int) {
self.no2 = no2
super.init(no1: no1)
}
// Requires only one parameter for convenient method
required override convenience init(no1: Int) {
self.init(no1: no1, no2: 0)
}
}
let res = MainClass(no1: 20)
let show = SubClass(no1: 30, no2: 50)
print("res is: \(res.no1)")
print("res is: \(show.no1)")
print("res is: \(show.no2)")
The above program execution output is:
res is: 20
res is: 30
res is: 50
Extending Access Levels
You can extend classes, structures, and enumerations under permissible conditions. The extension members should have the same access level as the original class members. For example, if you extend a public type, the new members should have the same default internal access level as the original members.
Alternatively, you can explicitly declare the access level for the extension (e.g., using private extension
) to give all members within the extension a new default access level. This new default access level can still be overridden by the access level declared for individual members.
Generic Access Levels
The access level for a generic type or generic function is the minimum access level of the generic type, the function itself, and the generic type parameters.
Example
public struct TOS<T> {
var items = [T]()
private mutating func push(item: T) {
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
}
var tos = TOS<String>()
tos.push("Swift")
print(tos.items)
tos.push("Generics")
print(tos.items)
tos.push("Type Parameters")
print(tos.items)
tos.push("Type Parameter Name")
print(tos.items)
let deletetos = tos.pop()
The above program execution output is:
["Swift"]
["Swift", "Generics"]
["Swift", "Generics", "Type Parameters"]
["Swift", "Generics", "Type Parameters", "Type Parameter Name"]
Type Aliases
Any type aliases you define are treated as distinct types for the purposes of access control. A type alias cannot have a higher access level than the type it aliases.
For example, a private type alias can be assigned to a public, internal, or private type, but a public type alias can only be assigned to a public type and not to an internal or private type.
>
Note: This rule also applies to type aliases for types that are required to conform to protocols.
Example
public protocol Container {
associatedtype ItemType
mutating func append(item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
struct Stack<T>: Container {
// original Stack<T> implementation
var items = [T]()
mutating func push(item: T) {
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
// conformance to the Container protocol
mutating func append(item: T) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> T {
return items[i]
}
}
func allItemsMatch<
C1: Container, C2: Container
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
(someContainer: C1, anotherContainer: C2) -> Bool {
// check that both containers contain the same number of items
if someContainer.count != anotherContainer.count {
return false
}
// check each pair of items to see if they are equivalent
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// all items match, so return true
return true
}
var tos = Stack<String>()
tos.push("Swift")
print(tos.items)
tos.push("Generics")
print(tos.items)
tos.push("Where Clause")
print(tos.items)
var eos = ["Swift", "Generics", "Where Clause"]
print(eos)
The above program execution output is:
["Swift"]
["Swift", "Generics"]
["Swift", "Generics", "Where Clause"]
["Swift", "Generics", "Where Clause"]