美文网首页
Swift 访问控制(Access Control)

Swift 访问控制(Access Control)

作者: 西风那个吹呀吹 | 来源:发表于2020-09-30 22:17 被阅读0次

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

  1. open : 允许在定义实体的模块、其他模块中访问。允许其他模块进行继承、重写(open只能用在类、类成员上)

模块概念如下图所示:

模块概念示例图
TARGETS下的Demo就是程序编译后的可执行文件,是一个独立的模块。也就是说,在此模块中,不管是main.swift文件还是test.swift文件,在此模块下的任意文件用到其他任意文件中的类或者结构体等其他数据,都不用像OC中那样import头文件。

当然,这里需要注意权限访问级别open是允许所有模块都可任意访问的,不管是工程可执行模块还是系统库模块又或者是其他第三方库模块。

动态库的一些想要公开出去给别人调用的类,一般声明open 访问权限,如下:

open class Person {}

注意:open 只能用在类上,不能用在结构体、枚举


  1. public : 允许在定义实体的模块、其他模块中访问,不允许其他模块继承、重写
  1. internal : 只允许在定义实体的模块中访问,不允许在其他模块中访问
  1. fileprivate : 只允许在定义实体的源文件中访问
main.swift test.swift
如上面两图所示,Person类定义在main.swift文件中,且用fileprivate访问级别修饰,就不能在test.swift等其他文件中调用,它只允许在自己的定义源文件main.swift中调用。
  1. private : 只允许在定义实体的封闭声明中访问
private 修饰变量
如图所示,变量nameprivate修饰,它的被访问权限仅限于封闭声明中,也就是大括号内。

通过上面这五个访问控制权限可以看出,Swift不同于其他语言,它是以模块、文件为单位控制访问级别权限的。


绝大部分实体默认都是internal级别

也就是说,不写访问控制级别的修饰关键字的话,默认都是添加了internal,整个项目都可访问。


访问级别的使用准则

  • 一个实体不可以被更低访问级别的实体定义,例如:
  1. 变量\常量类型 >= 变量\常量(变量类型的访问级别要大于等于变量的访问级别,同理,常量类型的访问级别要大于等于常量的访问级别)
    变量与类型不同访问级别
    如图所示,变量person的类型是Personinternal的访问级别是高于fileprivate,编译报错。
    也就是上面说的,一个实体不可以被更低访问级别的实体定义internal修饰的实体:变量person,不能被访问级别低于它的fileprivate修饰的类型Person定义。
    本质:其他地方访问类实例person,意味着也要能够访问类Person,就要保证Person的访问级别不能低于其实例person的访问级别,否则,访问实例的地方无法访问类信息,怎么用?很矛盾,无法正常使用
  1. 参数类型、返回值类型 >= 函数
    同理:别的地方调用函数,必然用到函数参数和返回值,这就意味着参数类型和返回值类型的访问级别必须不低于函数的访问级别。
    否则函数参数和返回值都无法访问,怎么能调用函数?
  1. 父类 >= 子类
  • 子类重写成员的访问级别必须 >= 子类的访问级别,或者>=父类被重写成员的访问级别(也就是说大于等于子类和父类被重写成员两者中的最低的那个访问级别)
  • 父类的成员不能被成员作用域外定义的子类重写(比如:private修饰的父类成员,不能被父类{}的子类重写,因为private作用域仅限于父类{}内,若是将子类放到父类{}内,则是没问题的)。
  1. 父协议 >= 子协议
  1. 原类型 >= typealias
class Person {}
public typealias MyPerson = Person

这样的是不行的,编译报错(Type alias cannot be declared public because its underlying type uses a indternal type)
Person定义MyPerson,也就是定义typealias,原类型Person的访问级别必须大于typealias的访问级别,显然这里的Person的默认访问级别是indternal,是低于public的。

  1. 原始值类型、关联值类型 >= 枚举类型
  2. 定义类型A时用到的其他类型 >= 类型A
  3. ......

元组类型

  • 元组类型的访问级别是所有成员类型最低的那个
internal class Person {}
fileprivate class Cat {}

fileprivate var aa: (Person, Cat)
internal    var bb: (Person, Cat) //编译报错Variable cannot be declared internal because its type uses a fileprivate type

如上代码示例,(Person, Cat)是元组类型,它的访问级别是所有成员中类型最低的那个,也就是此元组类型的访问级别是fileprivate
Swift 的访问控制级别规定,一个实体不可以被更低访问级别的实体定义
代码中,变量aa的访问的级别与定义它的元组类型的级别都是fileprivate,是附和访问级别的使用准则。
而变量bb的访问级别internal是高于定义它的元组类型(Person, Cat)的访问级别fileprivate,是不符合Swift访问级别语法规定的,所以编译报错。


泛型类型

  • 泛型类型的访问级别是类型的访问级别以及所有泛型类型参数的访问级别中最低的那个
internal class Cat {}
fileprivate class Dog {}
public class Person<T1, T2> {}

//Person<Cat, Dog>的访问级别是fileprivate
fileprivate var p = Person<Cat, Dog>()

代码示例中,Person<Cat, Dog>的访问级别是由类型Person和泛型参数类型CatDog三者中最低的那个决定的,最终确定为Dog的访问级别fileprivate
Person<Cat, Dog>的访问级别fileprivate是大于等于变量p的访问级别,编译通过。


成员、嵌套类型

  • 类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别
  1. 一般情况下,类型为privatefileprivate,那么成员、嵌套类型默认也是privatefileprivate
  2. 一般情况下,类型为internalpublic,那么成员、嵌套类型默认是internal
class Test {
    private class Person {}
    fileprivate class Student : Person {}//编译报错Class cannot be declared fileprivate because its superclass is private
}

父类Person的访问级别private是低于子类Studentfileprivate,所以编译报错。
但是将Test中的类放到类外面,也就是源文件中,全局作用域内,如下:

private class Person {}
fileprivate class Student : Person {}

是不报错的,编译能通过。
为什么呢?

因为private访问级别仅限于封闭声明中,此刻代码在全局作用域内,并没有在某个代码块中,所以,它的访问级别就是此源文件内,本质上与fileprivate的访问级别等同了,都是在实体源文件中。

private struct Dog {
    private var name: String = "Dog"
    private func eat() {};
}

fileprivate struct Cat {
    var dog: Dog = Dog()
    mutating func dosomethings() {
        dog.name = "张三" //编译报错'name' is inaccessible due to 'private' protection level
        dog.eat() //编译报错'eat' is inaccessible due to 'private' protection level
    }
}

这段代码为什么编译报错呢?

因为Dog成员访问级别是private,被限定在Dog作用域内了,外部是没有权限访问的。

private struct Dog {
    var name: String = "Dog"
    func eat() {};
}

fileprivate struct Cat {
    var dog: Dog = Dog()
    mutating func dosomethings() {
        dog.name = "张三"
        dog.eat()
    }
}

此段代码编译通过,为什么呢?前面不是说了类型为private或fileprivate,那么成员、嵌套类型默认也是private或fileprivate的吗?
既然Dog访问级别为private,那么它的成员nameeat()的访问级别也应该是private
为什么编译通过了呢?
因为全局作用域内,privatefileprivate的访问权限一样,都是此源文件内。
那么,private就是fileprivate,上面代码就等同于下面代码:

fileprivate struct Dog {
    fileprivate var name: String = "Dog"
    fileprivate func eat() {};
}

fileprivate struct Cat {
    var dog: Dog = Dog()
    mutating func dosomethings() {
        dog.name = "张三"
        dog.eat()
    }
}

注意:睁大眼睛,不要被private轻易迷惑,要看作用域的。
直接在全局作用域下定义的private等价于fileprivate

再看下边代码:

class Test {
    private struct Dog {
        var name: String = "Dog"
        func eat() {};
    }
    
    private struct Cat {
        var dog: Dog = Dog()
        mutating func dosomethings() {
            dog.name = "张三"
            dog.eat()
        }
    }
}

此刻,Dog不在全局作用域了,按说,它的属性name和方法eat()被默认修饰为private,不能被外部访问的,也就是Cat内无法调用dog.name 和 dog.eat()的。
为什么没有编译出错呢?

因为,Dog的访问级别private是限定在类Test内的,它的成员继承了它的private,这个private是限定在类Test内的private,不是其他private
也就是说,成员的privateDogprivate是一样的,都可以被Test作用域内访问。
若是手动明确添加写上访问限定符private var name: String = "Dog",那么这里的privateDogprivate是不一样的,它是明确告诉编译器,这里的属性name是不能被外部访问的。
PS:有点绕,只可意会不可言传,就当是基因遗传吧。


getter、setter

  • getter、setter默认自动接收它们所属环境的访问级别

可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限

//其他文件可以获取全局变量num的值,但是不能修改其值,只能在此文件内可修改值
fileprivate(set) public var num = 10
class Person {
    //age的setter访问级别被修饰为private,Person内部可访问,Person外部无法访问
    private(set) var age = 0
    
    //weight的setter访问级别被修饰为fileprivate,main文件内部可访问,main文件外部无法访问
    fileprivate(set) public var weight: Int {
        set {}
        get { 10 }
    }
    
    //subscript的setter访问级别被修饰为internal,模块内可访问,模块外无法访问
    internal(set) public subscript(index: Int) -> Int {
        set {}
        get { index }
    }
}

var p: Person = Person()
p.age = 22  //编译报错Cannot assign to property: 'age' setter is inaccessible

如上面代码,p.age = 22编译报错,因为存储属性agesetter访问级别被修饰为private,所以,age的写操作仅限于Person内部,其他地方无法使用写操作,也就不能修改其值。
由于agegetter没有被明确修饰为某个访问级别,默认是Person的访问级别,也就是internal,所以,读操作,模块内任何地方都是可以访问调用的。

注意:getter访问级别是不能比setter低的,上面案例中如private(set)这样的操作,只能用于setter上,不能用此方法设置getter的访问级别。

上面代码中,若是将private(set) var age = 0改为public(get) var age = 0,为什么编译会报错(Expected 'set' as subject of 'public' modifier)
因为看报错原因,编译器告诉我们,这种单独设置读写的访问权限的方式,它只能用于set,且访问级别不能低于get的访问级别。


初始化器

  • 如果一个public类 想在另一个模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器

因为public类 的默认初始化器是internal级别

public class Cat {
    var name: String?
}

Cat类的访问级别是public,其内部成员(属性、方法、下标)的访级别默认都是internal,也就是说默认的初始化器的访问级别也是internal
所以,其他另一个模块是无法访问此类默认初始化器的。

  • required初始化器 >= 它的默认访问级别

  • 如果结构体有private/fileprivate的存储实例属性,那么它的成员初始化器(带有成员的初始化器)也是private/fileprivate,否则默认就是internal

public struct Cat {
    private var name = "Cat"
    var age = 1
}
//编译报错,因为name的访问级别被限定在Cat{}内部,外部无法访问
var cat = Cat(name: "张三", age: 22)

还是要注意访问级别的问题,默认初始化器的访问级别是internal,但是存储属性的访问级别是private/fileprivate了,默认初始化器的访问级别也就是private/fileprivate了,private是在Cat类外无法调用默认初始化器的,fileprivate可以在源文件其他地方任意调用Cat类的默认初始化器,但是其他文件没有权限调用。


枚举类型的case

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

协议

  • 协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别
  • public协议定义的要求(协议方法)也是public
  • 协议实现的访问级别必须 >= 类型的访问级别,或者 >= 协议的访问级别

就是说,协议实现的访问级别必须是 >= 类型访问级别和协议访问级别中的最低的那个访问级别

fileprivate protocol Say {
    func sayHello()
}
public struct Cat : Say {
    fileprivate func sayHello() {}
}

如上代码,在Cat类内,协议的实现sayHello() {}访问级别fileprivate
类型Cat的访问级别是public
协议Say的访问级别是fileprivate
协议实现的访问级别是大于等于类型Cat和协议Say的访问级别中的最低的那个,编译通过。

public protocol Say {
    func sayHello()
}
public struct Cat : Say {
    func sayHello() {}
}

上面这段代码是编译不通过的,为什么?

因为前面说了,类型的访问级别是public,其内部成员访问级别默认都是internal
也就是说,func sayHello() {}等同于internal func sayHello() {},协议实现的访问级别internal低于public,编译报错。


扩展

  • 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别
//main.swift 文件
class Cat {}

private extension Cat {
    func sayHello() {}
}
//test.swift 文件
var cat = Cat()
cat.sayHello()
//编译报错'sayHello' is inaccessible due to 'fileprivate' protection level
  • 如上面两段代码,在test.swift文件中调用,编译报错,因为访问级别不够。
  • main.swift文件中,Cat扩展的访问级别是private,所以编译器报错'sayHello' is inaccessible due to 'fileprivate' protection level
    此处编译器报错中提示的是fileprivate,因为全局作用域内,private等同于fileprivate
  • 所以,猜测,应该是编译器做了优化,凡是全局作用域的private,就按fileprivate处理了。
  • 如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别跟直接在类型中定义的成员一样

  • 可以单独给扩展添加的成员设置访问级别

  • 不能给用于遵守协议的扩展显式设置扩展的访问级别

  • 在同一文件中的扩展,可以写成类似多个部分的类型声明

class Cat {
    private func say0() {}
    private func eat0() {
        say1()
    }
}
extension Cat {
    private func say1() {}
    private func eat1() {
        eat0()
    }
}
extension Cat {
    private func eat2() {
        say1()
    }
}

编译通过,为什么呢?私有private成员的访问级别不都是限定在当前定义的{}内吗?
其实,同一文件内的后面两个扩展相当于将类Cat拆分成不同部分,也就是说,上面的代码等同于下面的代码:

class Cat {
    private func say0() {}
    private func eat0() {
        say1()
    }
    private func say1() {}
    private func eat1() {
        eat0()
    }
    private func eat2() {
        say1()
    }
}
  • 在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
  • 在扩展中声明的一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它

将方法赋值给let、var

  • 方法也可以像函数那样赋值给一个let或者var
class Cat {
    func say(_ str: String) {
        print("Cat say:", str)
    }
    
    static func eat(_ food: String) {
        print("Cat eat:", food)
    }
}

let cat = Cat()
var fn = cat.say //fn类型为:(String) ->()
fn("呵呵")
//打印结果Cat say: 呵呵

var fn1 = Cat.say  //fn1类型为: (Cat) ->(String) ->()
var fn2 = fn1(Cat()) //fn2类型为:(String) ->()
fn2("哈哈")
//打印结果Cat say: 哈哈

var fn3 = Cat.eat
fn3("土豆") //fn3类型为:(String) ->()
//打印结果Cat eat: 土豆

相关文章

  • 访问控制

    访问控制(Access Control) 在访问控制这块,Swift提供5个不同的访问级别(以下是从高到低排列,实...

  • iOS-Swift-访问控制

    1. 访问控制(Access Control) 在访问权限控制这块,Swift提供了5个不同的访问级别(以下是从高...

  • [Swift5.1] 19-访问控制

    访问控制(Access Control) 在访问权限控制这块,Swift提供了5个不同的访问级别:(以下是从高到低...

  • 《Swift从入门到精通》(二十三):访问控制

    访问控制(Access Control)(学习笔记) 环境Xcode 11.0 beta4 swift 5.1欢迎...

  • Swift-OOP-权限控制

    访问控制(Access Control) 在访问权限控制这块,Swift提供了5个不同的访问级别(以下是从高到低排...

  • Swift进阶(十六)访问控制

    访问控制(Access Control) 在访问权限控制这块,Swift提供了5个不同的访问级别(以下是从高到低排...

  • swift 访问权限

    访问控制(Access Control) 在访问权限控制这块,Swift提供了5个不同的访问级别(以下是从高到低排...

  • 18-访问控制

    访问控制(Access Control) 在访问权限控制这块,Swift提供了5个不同的访问级别(以下是从高到低排...

  • Swift 访问控制(Access Control)

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

  • Swift新特性 -- 访问控制(Access Control)

    苹果在发布了Xcode 6 Bate 4后为Swift添加了新的特性--访问控制(Access Control),...

网友评论

      本文标题:Swift 访问控制(Access Control)

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