访问控制
在访问权限控制这块,Swift提供了5个不同的访问级别,以下是从高到低的叙述,其中
实体是指被访问级别修饰的内容
;open
:允许在定义实体的模块,其他模块中访问,允许其他模块进行继承,重写,open
只能用在类class,类成员上,不允许用在枚举与结构体上,这里的模块是指工程编译生成的Mach-O可执行文件
;-
public
:允许在定义实体的模块,其他模块中访问,不允许其他模块进行继承,重写,这里的模块同上;
-
internal
:只允许在定义实体的模块中访问,不允许其他模块访问,这里的模块是指子模块
,如下图所示:
fileprivate
:只允许在定义实体的源文件
中访问,即在指定的.swift文件中访问,在其他.swift文件中不能访问;private
:只允许在定义实体的封闭声明
中访问,例如在一个.swift文件中定义A,B两个Class类,那么A类中定义的实体只能在A类中访问,不能在B类中访问;绝大部分实体默认都是
internal
级别;
访问级别的使用准则
- 一个实体不可以被更低访问级别的实体定义;
- 变量类型的访问级别
大于等于
变量的访问级别; - 参数类型/返回值类型的访问级别
大于等于
函数的访问级别; - 父类的访问级别
大于等于
子类的访问级别; - 父协议的访问级别
大于等于
子协议的访问级别; - 原类型的访问级别
大于等于
typealias的访问级别; - 原始值,关联值类型的访问级别
大于等于
枚举的访问级别; - 定义类型A所用到的其他类型的访问级别
大于等于
A的访问级别;
- 变量类型的访问级别
元组类型
- 元组类型的访问级别是其所有成员类型最低的那个;
internal struct Dog { }
fileprivate class Person { }
fileprivate var data1: (Dog,Person)
private var data2: (Dog,Person)
var data3: (Dog,Person) //报错
-
(Dog,Person)
是元组类型,其中Dog的访问级别> Person的访问级别,所以(Dog,Person)元组的访问级别为fileprivate
- 现在用元组类型来定义变量,那么元组类型的访问级别必须
大于等于变量data1,data2,data3的访问级别,data3默认的访问级别为internal
,所以报错;
泛型
- 泛型类型的访问级别是
类型的访问级别
以及所有泛型类型参数的访问级别
中最低的那个;
internal class Car { }
fileprivate class Dog {}
public class Person<T1, T2> { }
fileprivate var p = Person<Car,Dog>()
-
< >
中的内容表示泛型,其访问级别是Person,Car,Dog这三个访问级别中最低的那个,所以泛型的访问级别为fileprivate;
成员,嵌套类型
- 类型的访问级别会影响成员(方法,属性,下标,初始化器),嵌套类型的默认访问级别;
- 一般情况下,类型为
private
或fileprivate
,那么成员,嵌套类型默认也为private
或fileprivate
- 一般情况下,类型为
internal
或public
,那么成员,嵌套类型默认是internal
,
fileprivate class Person {
var age = 0
func run() { }
enum Season {
case spring,summer,autum,winter
}
}
- 类型为
fileprivate
,那么其所有成员,也为fileprivate
public class Person {
var age = 0
func run() { }
enum Season {
case spring,summer,autum,winter
}
}
- 类型为
public
,其所有成员为internal
在main.swift文件中直接定义 代码如下:
import Foundation
private class Person { }
fileprivate class Student : Person { }
- 根据上面的规则,父类的访问级别要大于等于子类的访问级别,按理说会报错,但编译成功,原因在于
Person
与Student
,是在main.swift源文件的直接定义的,在整个main.swift源文件中都可以访问,如果做如下改动:
import Foundation
class TestClass {
private class Person { }
fileprivate class Student : Person { }
}
- 就会报错,因为Person的作用域仅局限于TestClass的内部,Student是在整个main.swift源文件中都可以访问,若在main.swift源文件中直接实例Student,就不能访问其父类Person,所以会报错;
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
}
}
}
- 编译通过,根据上面的规则,private struct Dog ,那么其成员age与run的访问级别也为private,然而dog.run,依然能在Person内部使用,原因在于成员age与run的访问级别的private是与类Dog的private的作用域是一致的,若改成下面的:
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()
dog.age = 1
}
}
}
- 编译报错,private func run(){},不能在Person中使用;
成员的重写
- 子类重写成员的访问级别必须 >= 子类的访问级别 或者 >= 父类被重写成员的访问级别;
public class Person {
fileprivate func run() {
}
}
public class Student : Person {
fileprivate override func run() {
}
}
getter,setter
- getter,setter默认自动接收它们所属环境的访问级别;
- 可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限;
class Person {
private(set) var age: Int = 0
fileprivate(set) public var weight: Int {
set {
}
get {
10
}
}
internal(set) public subscript(index: Int) -> Int {
set {
}
get {
index
}
}
}
var p: Person = Person()
p.age = 10 //不能写,报错
print(p.age)
初始化器
- 如果一个
public
类想在另一个模块调用编译生成的默认无参初始化器,必须显示提供public
的无参初始化器, 因为public
类的默认初始化器是internal
级别; -
required
初始化器 >= 它的默认访问级别
class Person {
fileprivate required init() {}
}
会报错,因为
class Person
的默认访问级别为internal
,根据上面的规则:required
初始化器 >= 它的默认访问级别,而现在required初始化器的访问级别为fileprivate
<internal
,所以报错;如果结构体有
private/fileprivate
的存储属性,那么它的成员初始化器也是private/fileprivate
,默认为internal
struct Point {
fileprivate var x = 0
var y = 0
}
var p: Point = Point(x: 10, y: 20)
枚举类型的case
- 不能给enum的每个case设置访问级别;
- 每个case自动接收enum的访问级别;
public enum Season {
case spring,summer,autum,winter
}
- enum是public
- 所有的case也都是public
协议
- 协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别,跟枚举类似;
- 协议实现的访问级别 >= 类型的访问级别 或者 >= 协议的访问级别
//协议的访问级别public
public protocol Runnable {
func run()
}
//类型的访问级别fileprivate
fileprivate class Person : Runnable{
//协议实现的访问级别internal
internal func run() {
}
}
扩展
- 如果有显示设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别;
- 如果没有显示设置扩展的访问级别,扩展添加的成员的默认访问级别与直接在类型中定义的成员一样;
- 可单独给扩展的成员设置访问级别;
- 不能给用于遵守协议的扩展显示设置扩展的访问级别;
protocol Runnable {
func run()
}
class Person{
}
fileprivate extension Person : Runnable { //报错
}
- 在同一文件中的扩展,可以写成类似多个部分的类型声明;
- 在原本的声明中定义一个私有成员,可以在同一文件的扩展中访问它;
- 在扩展中定义一个私有成员,可以在同一文件的其他扩展中,原本声明中访问它;
public class Person {
private func run0() {}
private func eat0() {
run1()
}
}
extension Person {
private func run1() {}
private func eat1() {
run0()
}
}
extension Person {
private func eat2() {
run1()
}
}
将方法赋值给let或者var
struct Person {
var age: Int
func run(_ v: Int) {
print("run",age,v)
}
static func run(_ v: Int) {
print("static run",v)
}
}
var fn1 = Person.run
fn1(20)
var fn2: (Person) -> (Int) -> () = Person.run
fn2(Person(age: 10))(20)
struct Person {
var age: Int
func run(_ v: Int) {
print("run",age,v)
}
static func run(_ v: Int) {
print("static run",v)
}
}
//(Person) -> (Int) -> ()
var fn = Person.run
//fn1是带有Person实例的run方法
//(Int) -> ()
var fn1 = fn(Person(age: 10))
//最后传入参数20,调用run方法
fn1(20)
内存管理
- 与OC一样,Swift也是采取基于引用计数的ARC内存管理方案;
- Swift的ARC中有3种引用:
- 强引用:默认情况下,都是强引用;
- 弱引用:通过
weak
来定义弱引用,必须是可选类型的var,因为实例销毁后,ARC会自动将若引用置为nil;ARC自动给弱引用设置为nil时,是不会触发属性观察器的;
- 无主引用(unowned reference):通过
unowned
定义无主引用,其不会产生强引用,实例对象销毁之后引用依然指向实例的内存地址,类似于OC中的unsafe_unretained
,试图在实例对象销毁后,访问无主引用,会导致野指针错误; -
weak
与unowned
只能应用于类的实例上面;
protocol Liveable : AnyObject {
}
class Person {
}
weak var p0: Person?
weak var p1: AnyObject?
weak var p2: Liveable?
unowned var p3: Person?
unowned var p4: AnyObject?
unowned var p5: Liveable?
AutoRelease
循环引用
-
weak
与unowned
都能解决循环引用的问题,但unowned
要比weak
少一些性能消耗; - 在实例对象的生命周期中会变成nil的,使用
weak
- 实例对象初始化赋值之后,再也不会变成nil的,使用
unowned
闭包的循环引用
- 闭包表达式默认会对用到的外层对象产生额外的强引用(即对外层对象进行了retain操作)
class Person {
var fn: (() -> ())?
func run() {
print("Person run")
}
deinit {
print("Person deinit")
}
}
func test() {
let p: Person = Person()
p.fn = {
p.run()
}
}
test()
- 闭包fn,会对p进行强引用,导致p无法被释放,造成内存泄漏;
- 解决方案:在闭包表达式的捕获列表声明
weak
或unowned
引用,解决循环引用问题;
class Person {
var fn: (() -> ())?
func run() {
print("Person run")
}
deinit {
print("Person deinit")
}
}
func test() {
let p: Person = Person()
p.fn = {
[weak p] in
p?.run()
}
}
test()
- 或者
class Person {
var fn: (() -> ())?
func run() {
print("Person run")
}
deinit {
print("Person deinit")
}
}
func test() {
let p: Person = Person()
p.fn = {
[unowned p] in
p.run()
}
}
test()
- 或者
class Person {
var fn: (() -> ())?
func run() {
print("Person run")
}
deinit {
print("Person deinit")
}
}
func test() {
let p: Person = Person()
p.fn = {
[weak wp = p] in
wp?.run()
}
}
test()
- 先看一个实例代码:
class Person {
lazy var fn: (() -> ()) = {
[weak weakSelf = self] in
weakSelf?.run()
}
func run() {
print("Person run")
}
deinit {
print("Person deinit")
}
}
func test() {
var p: Person = Person()
p.fn()
}
test()
- 上述闭包内部用到实例的成员,必须强制写上self,否则会报错,因为这样可以提醒开发者,可能会造成循环引用;
-
lazy var fn: (() -> ()) = { }
,只有使用到fn,才会去初始化加载fn,也就是说调用p.fn()
,才会初始化fn,可能会造成循环引用; - 再看一段代码:
class Person {
var age: Int = 0
lazy var getAge: Int = {
self.age
}()
deinit {
print("Person deinit")
}
}
func test() {
var p: Person = Person()
print(p.getAge)
}
test()
- 如果将
lazy
删除,self.age
会报错,这是因为self实例还没初始化,就调用getAge
,若加上lazy
,只有当实例初始化后才去调用getAge
,就不会报错了; - 如果lazy属性是闭包调用的结果,那么就不用考虑循环引用问题,因为闭包调用后,闭包的生命周期就结束了;
@escaping
- 非逃逸闭包,逃逸闭包,一般都是当作参数传递给函数;
- 非逃逸闭包:闭包调用发生在函数结束之前,闭包作用域在函数之内;
func test(_ fn: () -> ()) {
fn()
}
test {
print("1111")
}
-
fn
是非逃逸闭包,其调用在test函数内部; - 逃逸闭包:闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过
@escaping
进行声明;
import Dispatch
typealias Fn = () -> ()
var gFn: Fn?
func test1(_ fn: @escaping Fn) {
gFn = fn
}
func test2(_ fn: @escaping Fn) {
DispatchQueue.global().async {
fn()
}
}
-
fn
是逃逸闭包,其调用不在test1,test2的函数内部; - 逃逸闭包是不能捕获inout类型的参数的,因为逃逸闭包的调用是不确定的,而inout类型的参数是传入变量的内存地址给逃逸闭包,有可能变量已经销毁了,逃逸闭包再去修改变量所指向的内存;
局部作用域
- 在Swift中用
do { }
定义局部作用域;
class Dog {
var age: Int = 10
func run() {
}
}
do {
let dog1: Dog = Dog()
dog1.age = 20
}
do {
let dog2: Dog = Dog()
dog2.age = 20
}
- dog1在第一个do{}作用域内,dog2在第二个do{}作用域内,超出作用域,就会被释放回收;
内存访问冲突
- 既然是内存访问冲突,说明至少有两个访问,访问同一资源;
- 内存访问冲突的发生条件:
- 两个访问,至少有一个写入操作;
- 两个访问,访问的是同一块内存;
- 两个访问的访问时间重叠(比如在同一个函数内)
- 看下面一段代码:
var step = 1
func increment(_ num: inout Int) {
num += step
}
increment(&step)
- 报错:
Simultaneous accesses to 0x100008020, but modification requires exclusive access
-
num += step
即读取,写入同一块内存,且访问时间重叠,造成内存访问冲突,解决方案如下:
var step = 1
func increment(_ num: inout Int) {
num += step
}
var copyStep = step
increment(©Step)
step = copyStep
- 再看一段代码:
func balance(_ x: inout Int,_ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var num1 = 42
var num2 = 30
balance(&num1, &num2) //OK
balance(&num1, &num1) //Error
balance(&num1, &num1)
会报错,因为传参都是num1的内存地址,当执行y = sum - x
就会出现内存访问冲突;那么若传入一个结构体的两个不同成员,给balance函数,肯定会报错的,因为结构体成员都是存储在结构体中的,是同一块内存,所以会造成内存访问冲突;
若满足下面的条件,就说明重叠访问结构体的成员是安全的:
只访问实例存储属性,不是计算属性或类属性;
结构体是局部变量不是全局变量;
结构体没有被闭包捕获 或者 只被非逃逸闭包捕获;
指针
- 在Swift中有专门的指针类型,这些都被定性为不安全的,常见的有以下四种类型:
- UnsafePointer<Pointee> 类似于const Pointee *
- UnsafeMutablePointer<Pointee> 类似于Pointee *
- UnsafeRawPointer<Pointee> 类似于const void *
- UnsafeMutableRawPointer<Pointee> 类似于 void *
- 其中<Pointee>表示泛型,Mutable表示可修改;
var age = 10
func test1(_ ptr: UnsafeMutablePointer<Int>) {
//修改地址中的值
ptr.pointee = 20
print("test1",ptr.pointee)
}
func test2(_ ptr: UnsafePointer<Int>) {
//访问地址中的值
print("test2",ptr.pointee)
}
test1(&age)
test2(&age)
print(age) //20
var age = 10
func test3(_ ptr: UnsafeRawPointer) {
print("test3",ptr.load(as: Int.self))
}
func test4(_ ptr: UnsafeMutableRawPointer) {
ptr.storeBytes(of: 30, as: Int.self)
}
test3(&age)//10
test4(&age)
print(age)//30
- 由于UnsafeRawPointer是任意类型的指针,所以从它内存中取数据时,必须指定数据类型,它才知道取多少字节的数据,所以必须传入数据类型参数;
指针的应用实例
var arr = NSArray(objects: 11,22,33,44)
print(arr)
//UnsafeMutablePointer<ObjCBool> stop
arr.enumerateObjects { (element, idx, stop) in
print(idx,element)
//idx = 2时,停止遍历
if idx == 2 {
stop.pointee = true
}
}
-
stop
是UnsafeMutablePointer<ObjCBool> 指针类型;
获取指向某个变量的指针
//age的地址值为:0x100008188
var age = 10
//UnsafePointer<Int> 类型
//ptr的值为:0x100008188
var ptr1 = withUnsafePointer(to: &age) { $0 }
print(ptr1.pointee)
var ptr3 = withUnsafePointer(to: &age) { (p) -> UnsafePointer<Int> in
return p
}
//UnsafeMutableRawBufferPointer
var ptr2 = withUnsafeMutablePointer(to: &age) { $0 }
ptr2.pointee = 20
print(age)
var ptr4 = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer($0) }
ptr4.storeBytes(of: 40, as: Int.self)
var ptr5 = withUnsafePointer(to: &age) { UnsafeRawPointer($0)}
print(ptr5.load(as: Int.self)) //40
- 获取变量a的引用指针;
- 其中ptr1余ptr2是等价写法;
获取指向堆空间的指针
class Person {
}
//person是堆空间的地址值 即实例对象的地址值
var person: Person = Person()
//personPtr是全局区的地址值,也就是引用变量的地址值
var personPtr = withUnsafePointer(to: &person) { $0 }
print(personPtr)
print("1111")
class Person {
}
//person是指针变量 其值是堆空间的地址值
var person: Person = Person()
//personPtr是全局区的地址值,也就是引用指针变量person的地址值
var personPtr = withUnsafePointer(to: &person) { UnsafeRawPointer($0) }
print(personPtr)
//address是person对象堆空间的地址值
var address = personPtr.load(as: UInt.self)
//ptr2是指向address堆空间的指针变量
var ptr2 = UnsafeMutableRawPointer(bitPattern: address)
print(ptr2)
print("1111")
-
var ptr2 = UnsafeMutableRawPointer(bitPattern: address)
,根据堆空间获取一个指向堆空间的指针变量;
创建指针
//申请堆空间
var ptr = malloc(16)
//存 前8个字节
ptr?.storeBytes(of: 10, as: Int.self)
//存 后8个字节
ptr?.storeBytes(of: 20, toByteOffset: 8, as: Int.self)
//取 前8个字节
print((ptr?.load(as: Int.self))!) //10
//取 后8个字节
print((ptr?.load(fromByteOffset: 8, as: Int.self))!) //20
//销毁堆内存空间
free(ptr)
//另一种写法 -----------------------------------------------------
//申请堆空间
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
//存 前8个字节
ptr.storeBytes(of: 10, as: Int.self)
//存 后8个字节
ptr.advanced(by: 8).storeBytes(of: 20, as: Int.self)
//取 前8个字节
print(ptr.load(as: Int.self)) //10
//取 后8个字节
print(ptr.advanced(by: 8).load(as: Int.self)) //20
//销毁堆内存空间
ptr.deallocate()
//另一种写法 -----------------------------------------------------
//申请堆空间 8 * 2 = 16个字节
var ptr = UnsafeMutablePointer<Int>.allocate(capacity: 2)
//存 前8个字节
ptr.initialize(to: 10)
//存 后8个字节
ptr.successor().initialize(to: 20)
//取 前8个字节
print(ptr.pointee) //10
//取 后8个字节
print((ptr+1).pointee) //20
print(ptr[1]) //20
print(ptr.successor().pointee) //20
//销毁堆内存空间
ptr.deinitialize(count: 2)
ptr.deallocate()
class Person {
var age: Int
var name: String
init(age: Int,name: String) {
self.age = age
self.name = name
}
deinit {
print("Person deinit")
}
}
var ptr = UnsafeMutablePointer<Person>.allocate(capacity: 3)
ptr.initialize(to: Person(age: 10, name: "li"))
(ptr+1).initialize(to: Person(age: 20, name: "liyan"))
(ptr+2).initialize(to: Person(age: 30, name: "liyanyan"))
ptr.deinitialize(count: 3)
ptr.deallocate()
指针之间的转换
//任意类型指针
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
//转成int类型指针
var ptr2 = ptr.assumingMemoryBound(to: Int.self)
ptr.assumingMemoryBound(to: Int.self).pointee = 11
(ptr+8).assumingMemoryBound(to: Double.self).pointee = 22.0
//ptr.deallocate()
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
ptr.assumingMemoryBound(to: Int.self).pointee = 11
(ptr+8).assumingMemoryBound(to: Double.self).pointee = 22.0
//var ptr2 = unsafeBitCast(ptr, to: UnsafeMutablePointer<Int>.self)
print(unsafeBitCast(ptr, to: UnsafeMutablePointer<Int>.self).pointee)//11
print(unsafeBitCast(ptr, to: UnsafeMutablePointer<Double>.self).pointee)//22.0
ptr.deallocate()
-
unsafeBitCast
忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据;
class Person {
}
var person = Person()
//personObjectAddress存放的就是person的堆空间地址值
var personObjectAddress = unsafeBitCast(person, to: UnsafeRawPointer.self)
print(personObjectAddress)