美文网首页
iOS-Swift-访问控制

iOS-Swift-访问控制

作者: Imkata | 来源:发表于2020-01-17 15:32 被阅读0次

    1. 访问控制(Access Control)

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

    • open:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open只能用在类、类成员上)
    • public:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
    • internal:只允许在定义实体的模块中访问,不允许在其他模块中访问,绝大部分实体默认都是internal级别
    • fileprivate:只允许在定义实体的源文件中访问
    • private:只允许在定义实体的封闭声明中访问

    2. 访问级别的使用准则

    一个实体不可以被更低访问级别的实体定义,比如:
    变量\常量类型 ≥ 变量\常量
    参数类型、返回值类型 ≥ 函数
    父类 ≥ 子类
    父协议 ≥ 子协议
    原类型 ≥ typealias
    原始值类型、关联值类型 ≥ 枚举类型
    ......

    其实就是,定义类型A时用到的其他类型 ≥ 类型A,举例:

    //变量类型的访问级别大于等于变量的访问级别
    fileprivate class Person {} //但是Person类型是当前源文件可访问
    internal var person : Person //person变量是当前模块可访问
    //报错:Variable cannot be declared internal because its type uses a fileprivate type
    

    3. 元组类型

    元组类型的访问级别是所有成员类型最低的那个

    internal struct Dog {}
    fileprivate class Person {}
    //(Dog, Person)中Dog访问级别是internal,Person访问级别是fileprivate,所以(Dog, Person)元祖类型的访问级别是fileprivate
    
    //data1变量的访问级别也是fileprivate,大于等于元祖类型的访问级别,所以下面不报错
    fileprivate var data1: (Dog, Person)
    
    //同理,data2变量的访问级别是private,大于等于元祖类型的访问级别,所以下面也不报错
    private var data2: (Dog, Person)
    

    4. 泛型类型

    泛型类型的访问级别是类型的访问级别以及所有泛型类型参数的访问级别中最低的那个

    internal class Car {}
    fileprivate class Dog {}
    public class Person<T1, T2> {}
    
    //泛型类型Person<Car, Dog>,类型的访问级别是public,泛型类型参数的访问级别是internal、fileprivate
    //它们之中最低的那个是fileprivate,所以整个泛型类型的访问级别是fileprivate
    fileprivate var p = Person<Car, Dog>()
    

    5. 成员、嵌套类型

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

    1. 一般情况下,类型为private或fileprivate,那么成员、嵌套类型默认也是private或fileprivate
    2. 一般情况下,类型为internal或public,那么成员、嵌套类型默认是internal
    3. ⼀般情况下,类型为open,那么成员、嵌套类型默认是open
    public class PublicClass { //外面是public
        public var p1 = 0 // public
        var p2 = 0 // internal
        fileprivate func f1() {} // fileprivate
        private func f2() {} // private
    }
    
    class InternalClass { //默认是internal
        var p = 0 // internal
        fileprivate func f1() {} // fileprivate
        private func f2() {} // private
    }
        
    fileprivate class FilePrivateClass { //外面是fileprivate
        func f1() {} // fileprivate
        private func f2() {} // private
    }
    
    private class PrivateClass { //外面是private
        func f() {} // private
    }
    

    补充:对于上面的第1点,有个小细节说明

    class Test {
        private struct Dog {
             var age: Int = 0
             func run() {}
        }
    
        private struct Person {
            var dog: Dog = Dog()
            mutating func walk() {
                dog.run()
                dog.age = 1
            }
        }
    }
    

    上面代码不报错,但是下面代码就报错了

    class Test {
        private struct Dog {
            private var age: Int = 0
            private func run() {}
        }
        
        private struct Person {
            var dog: Dog = Dog()
            mutating func walk() {
                dog.run() //报错:run' is inaccessible due to 'private' protection level
                dog.age = 1 //报错:age' is inaccessible due to 'private' protection level
            }
        }
    }
    

    解释:

    1. 对于第一段代码,Dog是private的,所以Dog只能在Test{}里面访问,由于age、run()没有使用任何修饰词,所以age、run()默认也是private的,但是这个private不是只能在Dog{}里面访问,而是默认跟随Dog只能在Test{}里面访问,所以第一段代码不报错。
    2. 对于第二段代码,由于我们手动给age、run()加上了private,所以age、run()只能在Dog{}里面访问,所以第二段代码会报错。

    6. 成员的重写

    子类重写成员的访问级别必须 ≥ 子类的访问级别,或者 ≥ 父类被重写成员的访问级别(就是子类重写成员的访问级别⾄少⼤于等于两个之中最⼩的那个,类似于下面协议的实现)

    public class Person {
        public func run() {} //父类被重写成员的访问级别public
    }
    public class Student : Person { //子类的访问级别public
        override func run() {} //会报错:
        //如果不加public,子类的访问级别是public,里面的成员默认就是internal,internal小于子类的访问级别(public)或者父类被重写成员的访问级别(public),所以会报错
        //加上public就不报错了
    }
    

    父类的成员不能被成员作用域外定义的子类重写(⽐如⽤private修饰)

    public class Person {
        private var age : Int = 0 //父类的成员作用域是这个{}
    }
    public class Student : Person {
        override var age : Int { //报错:Property does not override any property from its superclass
            set {}
            get {10}
        }
    }
    

    如果放到里面就不会报错了,如下:

    public class Person {
        private var age: Int = 0
        
        public class Student : Person {
            override var age: Int {
                set {}
                get {10}
            }
        }
    }
    

    7. 下面代码能否编译通过?

    class Test {
        private class Person {}
        fileprivate class Student : Person {}
        //报错:Class cannot be declared fileprivate because its superclass is private
        //因为父类的访问级别比子类的访问级别小,那是肯定不行的
    }
    
    private class Person {}
    fileprivate class Student : Person {}
    //不报错,因为private写到外面去就相当于整个文件可以访问,这时候private就相当于fileprivate,所以上面不报错
    
    private struct Dog {
        var age: Int = 0
        func run() {}
    }
    
    fileprivate struct Person {
        //因为Dog是在全局作用域中定义的,所以private就相当于fileprivate,所以不会报错
        var dog: Dog = Dog() 
        mutating func walk() {
            dog.run()
            dog.age = 1
        }
    }
    
    private struct Dog {
        private var age: Int = 0
        private func run() {}
    }
    
    fileprivate struct Person {
        var dog: Dog = Dog()
        mutating func walk() {
        //报错,因为Dog是在全局作用域中定义的,所以private就相当于fileprivate
        //但是里面的成员age、run()是private,其他地方不能访问,所以报错
            dog.run() 
            dog.age = 1
        }
    }
    

    总结:直接在全局作用域中定义的private等价于fileprivate

    8. getter、setter

    getter、setter默认自动接收它们所属环境的访问级别
    可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限

    fileprivate(set) public var num = 10
    //setter是fileprivate,getter是public
    
    class Person { //默认是internal
        private(set) var age = 0 //setter是private,getter是默认internal
        fileprivate(set) public var weight: Int {
            set {}
            get { return 10 }
        }
        internal(set) public subscript(index: Int) -> Int {
            set {}
            get { return index }
        }
    }
    
    var p = Person()
    p.age = 10 //报错:Cannot assign to property: 'age' setter is inaccessible
    print(p.age)
    //可以发现,读不报错,写报错,因为读是internal,写是private
    

    9. 初始化器

    • 如果一个public类想在另一个模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器,因为public类的默认初始化器是internal级别
    //你自己写的动态库
    public class Person {
        public init(){ //需要加public
        }
    }
    
    //其他模块调用
    var p = Person()
    
    • required初始化器访问级别 ≥ 它的默认访问级别
    public class Person {
        private required init() {}
        //报错:类型为public,成员默认就是internal,这时候required初始化器访问级别(private)小于它的默认访问级别(internal),所以会报错
        //删除private,默认是internal,就不报错
    }
    
    • 如果结构体有private、fileprivate的存储实例属性,那么它的成员初始化器也是private、fileprivate,否则默认就是internal
    struct Point {
        fileprivate var x = 0 //如果加上fileprivate
        var y = 0
    }
    var p = Point(x: 10, y: 20)
    //不报错
    //上面加上fileprivate,这时候Point(x: 10, y: 20)初始化器也是fileprivate的,如果不加默认就是internal
    
    struct Point {
        private var x = 0 //如果加上private
        var y = 0
    }
    var p = Point(x: 10, y: 20)
    //报错:'Point' initializer is inaccessible due to 'private' protection level
    //如果加上private,那么Point(x: 10, y: 20)初始化器也是private的,所以上面会报错
    

    10. 枚举类型的case

    • 不能给enum的每个case单独设置访问级别
    • 每个case自动接收enum的访问级别,public enum定义的case也是public
      (上面说过:一般情况下,类型为internal或public,那么成员、嵌套类型默认是internal,但是枚举是例外,因为不能给枚举的每个case单独设置访问级别,所以public enum定义的case也是public)

    11. 协议

    • 协议中定义的要求,会自动接收协议的访问级别,不能单独设置访问级别。例如:public协议定义的要求也是public
    public protocol Runnable {
         func run() //可以这样写
    }
    
    protocol Runnable {
        public func run() //不能这样写
        //报错:'public' modifier cannot be used in protocols
    }
    
    • 协议实现的访问级别必须 ≥ 类型的访问级别,或者 ≥ 协议的访问级别(就是协议实现的访问级别⾄少⼤于等于两个之中最⼩的那个,类似于上面成员的重写)
    internal protocol Runnable { //协议的访问级别internal
        func run()
    }
    
    public class Person : Runnable { //类型的访问级别public
        internal func run() { //协议实现的访问级别大于等于上面两个中的最小的那个就可以
        //如果上面改成fileprivate或者private就会报错
        }
    }
    

    下面代码能编译通过么?

    public protocol Runnable {
        func run()
    }
    public class Person : Runnable {
        func run() {}
    }
    //不通过,协议实现默认internal,没有⼤于或等于协议定义的public
    

    12. 扩展

    • 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别
    • 如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样
    fileprivate extension Person {
        func run(){ //显式设置扩展的访问级别为fileprivate,那么run的访问级别也是fileprivate
        }
    }
    
    class Person {} //相当于写在这里面
    extension Person {
        func run(){ //没有显式设置扩展的访问级别,那么run的访问级别就和写在上面类里面没什么区别
        }
    }
    
    • 可以单独给扩展添加的成员设置访问级别
    extension Person {
        private func run(){ //单独给扩展添加的成员设置访问级别
        }
    }
    
    • 不能给用于遵守协议的扩展显式设置扩展的访问级别
    protocol Runnable {}
    class Person {}
    
    //报错:'fileprivate' modifier cannot be used with extensions that declare protocol conformances
    fileprivate extension Person : Runnable {
        func run(){
        }
    }
    
    • 在同一文件中的扩展,可以写成类似多个部分的类型声明
    //下面代码都在同一个文件中
    public class Person {
        private func run0() {}
        private func eat0() {
            run1() //原来类可以访问扩展中的run1
        }
    }
    
    //在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
    extension Person {
        private func run1() {}
        private func eat1() {
            run0() //扩展中可以访问原来类的run0
        }
    }
    
    //在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它
    extension Person {
        private func eat2() {
            run1() //扩展中可以访问扩展的run1
        }
    }
    

    上面代码,虽然方法是private的,但是在扩展和类中都可以相互调用,可以理解为把一个类的东西拆分成扩展了

    相关文章

      网友评论

          本文标题:iOS-Swift-访问控制

          本文链接:https://www.haomeiwen.com/subject/nhfqzctx.html