0%

Swift 访问控制

Swift 访问控制

继承是针对类的,重写是针对类的方法的。

In Module Out Module
open 可访问,可继承,可重写 可访问,可继承,可重写
public 可访问,可继承,可重写 可访问
internal 可访问,可继承,可重写 X
fileprivate 在同一源文件里可访问,可继承,可重写 X
private 如果修饰的是类中的成员,则成员只能在自身定义里或同一源文件中的自身扩展里可访问(也就是不能被重写了),如果修饰的是类则在同一源文件里可访问,可继承 X

open只能用于修饰类和类的成员(属性,方法和下标),不能修饰协议、全局变量/常量、结构体、枚举等等(这些可以使用public修饰)。系统禁止的原因可能是因为这些类型都是值类型本身就无法继承和重写,刚好就是public的作用。

1
2
3
4
5
6
7
8
9
10
11
// Only classes and overridable class members can be declared 'open'; use 'public'

open protocol MyProtocol: AnyObject { //报错

}

open let kAPIKey = "xxx" //报错

open struct MyStruct { //报错

}

对于初始化器如果需要对外公开,则只能使用public.如果子类的初始化器和父类的初始化器签名相同则子类还需要加上override关键字.是不是感觉有点疑惑,不是说public的在其他Module只能访问吗,为啥public修饰的初始化器在其他Module还可以override?无他—龟腚.

1
2
3
4
5
6
7
open class Machine {
public var numberID: String

open init(_ numberID: String) { //这里报错:Only classes and overridable class members can be declared 'open'; use 'public'
self.numberID = numberID
}
}

唯一找到的解释如下:

Allow distinguishing between public access and public overridability

Initializers do not participate in open checking; they cannot be declared open, and there are no restrictions on providing an initializer that has the same signature as an initializer in the superclass. This is true even of required initializers. A class’s initializers provide an interface for constructing instances of that class that is logically distinct from the interface of its superclass, even when signatures happen to match and there are well-understood patterns of delegation. Constructing an object of a subclass necessarily involves running code associated with that subclass, and there is no value in arbitrarily restricting what initializers the subclass may declare.

大意就是初始化器不参与open检查,并且它们也不能被声明为open.没有啥限制说子类的初始化器不能和父类的初始化器相同(实际上如果相同又必须添加override简直哔了狗).

fileprivate 和 private 区别

fileprivate只要在同一源文件那么就可以访问。而private则范围更窄只能在自身定义里或同一源文件中的自身扩展里。举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import UIKit

class MATBaseViewController: UIViewController {

fileprivate var name = ""
private var age = 0

deinit {
DLog("\(self)销毁")
}

override func viewDidLoad() {
super.viewDidLoad()

view.backgroundColor = .white
}
}

extension MATBaseViewController {
func printInfo() {
print(self.name)
print(self.age)
}
}

class Person: NSObject {
override init() {
let vc = MATBaseViewController.init()
vc.name = "xxx" //name可以被访问到,但是age不能
// vc.age = 3 //'age' is inaccessible due to 'private' protection level
}
}

访问级别规则

访问级别遵循的一般规则

不能在一个较低访问级别实体中定义一个较高访问级别的实体.

比如一个public的变量就不能定义在一个internal或private的类型中.一个函数的访问级别不能超过它的参数和返回值的类型的访问级别.

默认的访问级别

默认的访问级别是internal.

但是一个类型的访问控制级别会影响类里面的成员的默认访问级别.

比如:定义了一个private或fileprivate的类型,那么它的成员默认的访问级别将也是private或fileprivate(实际上好像不是这样的比如:一个类定义为private,但子类依然可以重写它的方法.).

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private class Food: NSObject { //同一源文件里可访问,可继承
var name = ""
func hello() {

}

private func run() { //还是需要加private 否则子类里还是能重写。

}
}

private class Orange: Food {
override func hello() {
super.hello()
print(name)
}

//Method does not override any method from its superclass
// override func run() {
// print(name)
// }
}

但是如果你定义的是一个public的类型,那么它的成员默认的访问级别将会是internal而不是public.也就是默认的访问控制级别最高不超过internal.

还有更多23个规则,可以在官方的Swift编程指南中找到.感觉Swift虽然号称简洁优雅,但是背后的规则细节不是一般的多.

framework访问控制

在制作对外使用的SDK时,如果某些类或方法需要暴露给外部使用,可以在对应类或方法前添加open或public修饰符.

不过Swift的SDK和OC的SDK不同的地方在于Swift SDK没有对外公开的头文件概念,这个很好理解,因为Swift本身就没有头文件.Swift会根据你所使用的访问控制修饰符自动生成对外接口.

当其他Module import我们的SDK后.可以点击进去查看我们提供的API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Foundation
import MyFrameworkDemo //这个是SDK

class People: NSObject {
var name: String?

override init() {
super.init()

let _ = Car(with: "sd")
let m = Machine.init("x")
m.numberID = "df"
}
}

点击进去查看系统生成的对外接口文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public var MyFrameworkDemoVersionNumber: Double
open class Car {

open var open_name: String

public var public_wheel: Int

public init(with name: String)

public func public_run()

open func open_hello()
}

open class Machine {

public var numberID: String

public init(_ numberID: String)
}

注意:根据上述表总结,可以看出系统只会将open或public修饰的类或方法生成到对外接口文件中.而internal及以下修饰的类将不会出现在对外接口文件中.

觉得文章有帮助可以打赏一下哦!