iOS-Swift-访问控制

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

  • open:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写,所以只能用在类、类成员上。
  • public:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写。
  • internal:只允许在定义实体的模块中访问,不允许在其他模块中访问,直接在一个文件中定义的实体默认就是internal级别
  • fileprivate:只允许在定义实体的源文件中访问。
  • private:只允许在定义实体的封闭声明中访问。

1. 嵌套类型、成员

我们知道,直接在一个文件中定义的类型的访问级别默认是internal,类型的访问级别会影响成员(属性、方法、初始化器、下标),如下:

  1. ⼀般情况下,类型为open,那么成员、嵌套类型默认是open。
  2. 一般情况下,类型为public或internal,那么成员、嵌套类型默认是internal。
  3. 一般情况下,类型为fileprivate或private,那么成员、嵌套类型默认也是fileprivate或private。
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  //没手动加private
         func run() {}     //没手动加private
    }

    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
        private func run() {}     //手动加private
    }
    
    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{}里面访问,所以第二段代码会报错。

2. 继承、重写

  1. 子类的访问级别必须小于等于父类的访问级别。

如下,父类Person默认访问级别是internal,子类访问级别是public,大于父类的访问级别了,所以会报错。如果不写(默认internal),或者改成private、fileprivate都不会报错。

class Person {} //默认internal
public class Student : Person {} //public,报错
  1. 子类重写成员的访问级别必须大于等于父类那个成员的访问级别。

如下,Person的访问级别默认是internal,那么它的run默认也是internal,子类Student的访问级别默认是internal,如果子类重写后run的访问级别改成fileprivate,就会报错,因为重写后的run必须大于等于internal。

class Person {
    func run() {}
}
class Student : Person {
    fileprivate override func run() {} //报错
}
  1. 父类的成员不能被成员作用域外定义的子类重写(⽐如⽤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}
        }
    }
}
  1. 直接在文件中定义的private就相当于fileprivate
private class Person {}
fileprivate class Student : Person {}
//不报错,因为private写到外面去就相当于整个文件可以访问,这时候private就相当于fileprivate,所以上面不报错

3. 最低原则

① 元组类型

元组类型的访问级别是所有成员类型最低的那个,因为这样才能保证元祖的内容都可以访问。

如下,(Dog, Person)中Dog访问级别是internal,Person访问级别是fileprivate,所以(Dog, Person)元祖类型的访问级别是fileprivate。

internal struct Dog {}
fileprivate class Person {}

如下,data1变量的访问级别是fileprivate,等于元祖类型的访问级别,所以下面不报错:

fileprivate var data1: (Dog, Person)

② 泛型类型

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

如下代码,泛型类型Person<Car, Dog>,类型的访问级别是public,泛型类型参数的访问级别是internal、fileprivate,它们之中最低的那个是fileprivate,所以整个泛型类型的访问级别是fileprivate,所以下面代码不会报错:

internal class Car {}
fileprivate class Dog {}
public class Person<T1, T2> {}

fileprivate var p = Person<Car, Dog>()  //不会报错

4. 自动接收原则

① 枚举类型的case

不能给enum的每个case单独设置访问级别,每个case自动接收enum的访问级别,例如:public enum定义的case也是public。

② 协议

  1. 协议中定义的内容,会自动接收协议的访问级别,不能单独设置访问级别,例如:public协议定义的内容也是public。
protocol Runnable {
    public func run() //不能这样写
    //报错:'public' modifier cannot be used in protocols
}
  1. 协议实现的访问级别必须大于等于协议里那个方法的访问级别
internal protocol Runnable { //协议的访问级别internal
    func run() //自动接收也是internal
}

public class Person : Runnable { //类型的访问级别public
    internal func run() { //不报错
    //如果上面改成fileprivate或者private就会报错
    }
}

下面代码能编译通过么?

public protocol Runnable {
    func run()
}
public class Person : Runnable {
    func run() {} //报错
    //协议定义的run方法跟随协议定义也是public,但是协议实现是internal,这肯定是不可以的
}

③ 扩展

  1. 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别。
  2. 如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别跟直接在类型中定义的成员一样。
fileprivate extension Person {
    func run(){ //显式设置扩展的访问级别为fileprivate,那么run的访问级别也是fileprivate
    }
}

class Person {} //相当于写在这里面
extension Person {
    func run(){ //没有显式设置扩展的访问级别,那么run的访问级别就和写在上面类里面没什么区别
    }
}
  1. 可以单独给扩展添加的成员设置访问级别
extension Person {
    private func run(){ //单独给扩展添加的成员设置访问级别
    }
}
  1. 不能给用于遵守协议的扩展显式设置扩展的访问级别
protocol Runnable {}
class Person {}

//报错:'fileprivate' modifier cannot be used with extensions that declare protocol conformances
fileprivate extension Person : Runnable {
    func run(){
    }
}
  1. 在同一文件中的扩展,可以写成类似多个部分的类型声明
//下面代码都在同一个文件中
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的,但是在扩展和类中都可以相互调用,可以理解为把一个类的东西拆分成扩展了。

5. 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

6. 初始化器

  1. 如果一个模块想调用另一个模块public类在编译生成的默认无参初始化器,那么这个public类必须显式提供public的无参初始化器,因为这个public类的默认初始化器是internal级别的。
//你自己写的动态库
public class Person {
    public init(){ //需要加public
    }
}

//其他模块调用
var p = Person()
  1. required初始化器访问级别必须要大于等于它的默认访问级别,因为如果小于它的默认访问级别,子类就没法重写了。
public class Person {
    private required init() {}
    //报错:类型为public,成员默认就是internal,这时候required初始化器访问级别(private)小于它的默认访问级别(internal),所以会报错
    //删除private,默认是internal,就不报错
}
  1. 如果结构体有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的,所以上面会报错
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容