Swift access control
Access control limits the level of access that code in other source files or modules can have to your code.
You can explicitly set access levels for individual types (classes, structs, enumerations), and for properties, functions, initializers, primitive types, subscripts, etc. of these types.
Protocols can also be limited to a certain scope, including global constants, variables, and functions in the protocol.
Access control is based on modules and source files.
A module refers to a Framework or Application that is built and distributed as an independent unit. A module in Swift can import another module using the import keyword.
A source file is a single source code file, which usually belongs to a module, and a source file can contain multiple class and function definitions.
Swift provides four different access levels for entities in code: public, internal, fileprivate, private.
access level | definition |
---|---|
public | You can access any entity in the source file in your own module, and others can also access all entities in the source file by introducing this module. |
internal | You can access any entity in the source file in your own module, but others cannot access the entity in the source file in the module. |
fileprivate | Intra-file private and can only be used within the current source file. |
private | It can only be accessed in the class, and cannot be accessed outside the scope of this class or structure. |
public is the highest access level and private is the lowest.
grammar
Declare the access level of the entity through 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 otherwise specified, entities use the default access level internal.
If no access level is specified, it defaults to internal
class SomeInternalClass {} // Access level is internal let someInternalConstant = 0 // Access level is internal
function type access
The access level of a function needs to be derived from the access level of the function's parameter types and return type.
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 in the function, SomeInternalClass, has an access level of internal, and the other, SomePrivateClass, has an access level of private. Therefore, according to the principle of tuple access level, the access level of the tuple is private (the access level of the tuple is consistent with the type with the lowest access level in the tuple).
Because the access level of the function's return type is private, you must explicitly declare the function with the private modifier:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) { // function implementation }
It is an error to declare the function as public or internal, or to use the default access level of internal, because then you cannot access the return value of the private level.
enum type access
The access level of the members in the enumeration is inherited from the enumeration, and you cannot declare different access levels for the members in the enumeration.
example
For example, in the following example, the enumeration Student is explicitly declared as a public level, then its members Name and Mark have access levels that are also public:
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's result: \(Mark1),\(Mark2),\(Mark3)") }
The output of the above program execution is:
student's result: 98,97,95
subclass access
The access level of the subclass must not be higher than that of the parent class. For example, if the access level of the parent class is internal, the access level of the subclass cannot be declared as public.
example
public class SuperClass { fileprivate func show() { print("super class") } } // 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 output of the above program execution is:
super class Subclass
Constant, variable, attribute, subscript access
Constants, variables, and properties cannot have a higher access level than their type.
For example, you define a public-level attribute, but its type is private-level, which is not allowed by the compiler.
Likewise, a subscript cannot have a higher access level than the index type or return type.
If the definition types of constants, variables, attributes, and subscript indexes are private, then they must explicitly declare that the access level is private:
private var privateInstance = SomePrivateClass()
Getter and Setter access permissions
The access level of constants, variables, properties, and subscripted Getters and Setters is inherited from the access level of the members they belong to.
The access level of the Setter can be lower than the access level of the corresponding Getter, so that the read and write permissions of variables, attributes or subscript indexes can be controlled.
example
class Samplepgm { fileprivate var counter: Int = 0{ willSet(newTotal){ print("counter: \(newTotal)") } didSet{ if counter > oldValue { print("newly added quantity \(counter - oldValue)") } } } } let NewCounter = Samplepgm() NewCounter.counter = 100 NewCounter.counter = 800
The access level of counter is fileprivate, which can be accessed within the file.
The output of the above program execution is:
counter: 100 New added quantity 100 counter: 800 Newly added quantity 700
Constructor and Default Constructor Access
initialization
We can declare the access level for the custom initialization method, but it should not be higher than the access level of the class to which it belongs. An exception is required constructors, whose access level must be the same as that of the class to which they belong.
Like function or method parameters, initialization method parameters cannot have a lower access level than the initialization method.
default initialization method
Swift provides a default no-argument initialization method for structures and classes, which is used to provide assignment operations for all their properties, but does not give specific values.
Default initializers have the same access level as the owning type.
example
Declare access permissions using the required keyword before each subclass's init() method.
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 output of the above program execution is:
10 30 10
Protocol Access
If you want to explicitly declare the access level for a protocol, you need to pay attention to that you need to ensure that the protocol is only used in the scope of the access level you declare.
If you define a protocol with public access level, the necessary functions provided to implement the protocol will also have public access level. This is different from other types, for example, other types of public access level, and their members have an internal access level.
example
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 output of the above program execution is:
res is: 20 res is: 30 res is: 50
Extended access
You can extend classes, structures, and enumerations as conditions permit. Extension members should have the same access level as the original class members. For example, if you extend a public type, then your newly added members should have the same default internal access level as the original members.
Alternatively, you can explicitly declare an extension's access level (such as using a private extension) to declare a new default access level for all members of the extension. This new default access level can still be overridden by individual member declared access levels.
generic access
The access level of a generic type or a generic function is the lowest access level among 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("generic") print(tos.items) tos.push("type parameter") print(tos.items) tos.push("type parameter name") print(tos.items) let deletetos = tos.pop()
The output of the above program execution is:
["Swift"] ["Swift", "generic"] ["Swift", "generic", "type parameter"] ["Swift", "generic", "type parameter", "type parameter name"]
type alias
Any type aliases you define will be treated as distinct types for access control purposes. The access level of a type alias cannot be higher than that of the original type.
For example, a private-level type alias can be set to a public, internal, or private type, but a public-level type alias can only be set to a public-level type, and cannot be set to an internal or private-level type . Note: This rule also applies when aliasing related types for protocol conformance.
example
public protocol Container { typealias 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("generic") print(tos.items) tos.push("Where sentence") print(tos.items) var eos = ["Swift", "generic", "Where sentence"] print(eos)
The output of the above program execution is:
["Swift"] ["Swift", "generic"] ["Swift", "generic", "Where sentence"] ["Swift", "generic", "Where sentence"]