Files
knowledge-kit/Chapter1 - iOS/1.120.md
2025-06-23 01:18:55 +08:00

6.7 KiB
Raw Blame History

Swift 访问控制

访问控制

在访问权限控制这块Swift 提供了5个不同的访问级别(以下是从高到低排列, 实体指被访问级别修饰的内容)

  • open允许在定义实体的模块、其他模块中访问允许其他模块进行继承、重写open只能用在类、类成员上)
  • public允许在定义实体的模块、其他模块中访问不允许其他模块进行继承、重写
  • internal只允许在定义实体的模块中访问不允许在其他模块中访问
  • fileprivate只允许在定义实体的源文件中访问
  • private只允许在定义实体的封闭声明中访问

绝大部分实现默认都是 internal

使用准则

各种 case 如下:

  • 变量类型 >= 变量

    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

    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。

  • 原始值类型、关联值类型 >= 枚举类型

    // 编译通过
    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

  • 元祖类型:元祖类型的访问级别是所有成员类型最低的那个(木桶原理)

    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 为 internalDog 为 fileprivate所以最低的是 fileprivate所以 Person 类型的访问级别为 fileprivate。因此 Person 类型变量的访问级别为 fileprivate 或者比 fileprivate 更低的访问级别。
    • Swift 默认所有的变量、方法、类的默认访问级别为 internal。internal 访问级别高于 fileprivate所以报错。
    internal class Car { }
    fileprivate class Dog { }
    public class Person<T1, T2> { }
    
    var person: Person<Car, Dog> = Person() // compile error: Variable must be declared private or fileprivate because its type uses a fileprivate type
    

    改进下: fileprivate var person: Person<Car, Dog> = Person()private var person: Person<Car, Dog> = Person() 都不会报错。

  • 类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别:

    • 一般情况下,类型为 fileprivate、private那么成员、嵌套类型默认也是 private、fileprivate
    • 一般情况下,类型为 internal、public那么成员、嵌套类型默认也是 internal

    测试:

    // 编译不通过
    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 权限更低的访问级别,用以限制写权限
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。另一个模块是无法访问的

    // APM.dylib
    public class APMManager {
        public init () {
    
        }
    }
    
    // 另一个模块(动态库/静态库/主工程)
    var manager = APMManager() 
    

枚举类型

不能给 enum 的 case 单独设置访问级别,每个 case 自动对齐 enum 的访问级别比如public 的 enum各个 case 也是 public

协议

协议中定义的要求自动接收协议的访问级别不能单独设置访问级别比如public 定义的协议,各个属性、方法也是 public 级别) 协议实现的访问级别 >= 类型的访问级别(协议的访问级别)

// 编译通过
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'
}