# Swift 访问控制 ## 访问控制 在访问权限控制这块,Swift 提供了5个不同的访问级别(以下是从高到低排列, 实体指被访问级别修饰的内容) - open:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open只能用在类、类成员上) - public:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写 - internal:只允许在定义实体的模块中访问,不允许在其他模块中访问 - fileprivate:只允许在定义实体的源文件中访问 - private:只允许在定义实体的封闭声明中访问 绝大部分实现默认都是 internal ## 使用准则 各种 case 如下: - 变量类型 >= 变量 ```swift fileprivate class Person { func sayHi() {} } internal var p:Person = Person() // Variable cannot be declared internal because its type uses a fileprivate type class Dog { func sepak() { p.sayHi() } } ``` 编译器报错:`Variable cannot be declared internal because its type uses a fileprivate type`。 访问权限前后冲突了。变量 p 被声明为 internal,也就是其他类中也可以使用该对象。但 Person 类被 fileprivate 修饰了,也就是只可以在定义该类的文件中使用。所以会冲突 - 参数类型、返回值类型 >= 函数 - 父类 >= 子类 因为定义的子类如果是 internal,而父类是 fileprivate,则可以访问到子类的地方,访问不到父类,这是不合理的。 - 父协议 >= 子协议 协议也可以继承,父协议里的一些属性如果访问级别较低,遵循子协议,实现子协议的类,是无法访问到父协议中的属性的。 - 原类型 >= typealias ```swift class Person {} fileprivate typealias People = Person // 编译通过 public typealias People = Person // Type alias cannot be declared public because its underlying type uses an internal type ``` `Person` 默认是 internal,所以 typealias 的权限修饰符需要小于等于 internal。 - 原始值类型、关联值类型 >= 枚举类型 ```swift // 编译通过 typealias OwnInt = Int typealias OwnString = String internal enum DataKind { case int(OwnInt) case string(OwnString) } // 编译失败 fileprivate typealias OwnInt = Int fileprivate typealias OwnString = String public enum DataKind { case int(OwnInt) // Enum case in a public enum uses a fileprivate type case string(OwnString) // Enum case in a public enum uses a fileprivate type } ``` - 定义类型 A 时用到的其他类型 >= 类型A - 元祖类型:元祖类型的访问级别是所有成员类型最低的那个(木桶原理) ```swift internal struct Dog {} fileprivate class Person {} internal var data1:(Dog, Person) // Error:Variable cannot be declared internal because its type uses a fileprivate type fileprivate var data1:(Dog, Person) private var data2:(Dog, Person) ``` - 泛型类型:泛型类型的访问级别由:类型的访问级别以及所有泛型类型参数的访问级别最低的那个决定 Demo1: 编译器报错:`Variable must be declared private or fileprivate because its type uses a fileprivate type` 分析: - 泛型的访问级别由最低的类别的访问级别决定。Car 为 internal,Dog 为 fileprivate,所以最低的是 fileprivate,所以 Person 类型的访问级别为 fileprivate。因此 Person 类型变量的访问级别为 fileprivate 或者比 fileprivate 更低的访问级别。 - Swift 默认所有的变量、方法、类的默认访问级别为 internal。internal 访问级别高于 fileprivate,所以报错。 ```swift internal class Car { } fileprivate class Dog { } public class Person { } var person: Person = Person() // compile error: Variable must be declared private or fileprivate because its type uses a fileprivate type ``` 改进下: `fileprivate var person: Person = Person()` 和 `private var person: Person = Person()` 都不会报错。 - 类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别: - 一般情况下,类型为 fileprivate、private,那么成员、嵌套类型默认也是 private、fileprivate - 一般情况下,类型为 internal、public,那么成员、嵌套类型默认也是 internal 测试: ```swift // 编译不通过 class TestClass { private class Person {} fileprivate class Student : Person {} // Class cannot be declared fileprivate because its superclass is private } // 编译通过 private class Person {} fileprivate class Student : Person {} ``` - 当 fileprivate、private 都写在文件的全局作用域时,访问权限是一样的。 总结:看似规则比较多,实际上就是对「一个实体不可以被更低访问级别的实体定义」的解释。 ## getter、setter - getter、setter 默认自动接收他们所属换的访问级别 - **可以给 setter 单独设置一个比 getter 权限更低的访问级别,用以限制写权限** ```swift private(set) var num = 10 class Person { private(set) var age = 0 init(age: Int = 0) { self.age = age } public var weight: Int { set {} get { 10 } } } var p = Person(age: 10) //print(p.age) //p.age = 20 print(num) num = 20 print(num) ``` ## 初始化器 - 如果一个 public 类,想在另一个模块调用编译生成的默认无参初始化器,必须显示提供 public 的无参初始化器(因为 public class,编译器生成的 init 初始化器是 internal。另一个模块是无法访问的) ```swift // APM.dylib public class APMManager { public init () { } } // 另一个模块(动态库/静态库/主工程) var manager = APMManager() ``` ## 枚举类型 不能给 enum 的 case 单独设置访问级别,每个 case 自动对齐 enum 的访问级别(比如:public 的 enum,各个 case 也是 public) ## 协议 协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别(比如:public 定义的协议,各个属性、方法也是 public 级别) **协议实现的访问级别 >= 类型的访问级别(协议的访问级别)** ```swift // 编译通过 public protocol Runnable { func run() } internal class Person : Runnable { internal func run() { } } // 编译失败 public protocol Runnable { func run() } public class Person : Runnable { internal func run() { } // Method 'run()' must be declared public because it matches a requirement in public protocol 'Runnable' } ```