访问控制、内存管理
访问控制(Access Control)
- 访问控制权限,5个不同的访问级别(以下为由高到低排列,实体指被访问级别修饰的内容)
open
允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open只能用在类、类成员上)public
允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写internal
只允许在定义实体的模块中访问,不允许在其他模块中访问fileprivate
只允许在定义实体的源文件中访问private
只允许在定义实体的封闭声明中访问
- 绝大多数实体默认都是 internal 级别
访问级别的使用准则
- 一个实体不可以被更低访问级别的实体定义,比如:
- 变量\常量类型 >= 变量\常量
- 参数类型、返回值类型 >= 函数
- 父类 >= 子类
- 父协议 >= 子协议
- 原类型 >= typealias
- 原始值类型、关联值类型 >= 枚举类型
- 定义类型A时用到的其他类型 >= 类型A
- .....
元组类型
元组类型的访问级别是所有成员类型最低的那个
internal struct Dog {}
fileprivate class Person {}
// (Dog, Person)的访问级别是 fileprivate
fileprivate var data1: (Dog, Person)
private var data2: (Dog,Person)
泛型类型
泛型类型的访问级别是 类型的访问级别 以及 所有泛型类型参数的访问级别 中最低的那个
成员、嵌套类型
- 类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认范文级别
- 一般情况下,类型为 private 或 fileprivate, 那么成员\嵌套类型默认也是 private 或 fileprivate
- 一般情况下,类型为 internal 或 public,那么成员\嵌套类型默认是 internal
- 成员的重写:
- 子类重写成员的访问级别必须 >=子类的访问级别,或者>=父类被重写成员的访问级别
- 父类的成员不能被成员作用域外定义的子类重写
public class Person {
private var age: Int = 0
}
public class Student : Person {
// 报错,不能被重写
override var age: Int {
set { }
get { 10 }
}
}
//1 下面代码编译会报错吗
private class Person {}
fileprivate class Student: Person {}
// 如果是在全局作用区,不会报错因为 private 就是在当前文件作用域 ,和fileprivate作用域相同, 如果在别的内部那就会报错
直接在全局作用域下定义的 private 等价于 fileprivate
getter setter
- getter setter 默认自动接收它们所属环境的访问级别
- 可以给 setter 单独设置一个比 getter 更低的访问级别,用以限制写的权限
class Person {
private(set) var age = 10
}
var p = Person()
p.age
p.age = 10 // 不可以被赋值 setter
初始化器
- 如果一个public类想在另一模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器
- 因为public类的默认初始化器时 internal级别
- required初始化器 >= 它的默认访问级别
- 如果结构体有 private\fileprivate 的存储实例属性,那么它的成员初始化器也是 private\fileprivate
struct Point {
private var x = 0
var y = 0
}
var p = Point(x:0) // 报错 不能访问
枚举类型的case
- 不能给 enum 的每个 case 单独设置访问级别
- 每个 case 自动接收 enum 的访问级别
- public enum 定义的 case 也是 public
协议
- 协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别
- public协议定义的要求也是 public
- 协议实现的访问级别必须 >= 类型的访问级别,或者 >=协议的访问级别
扩展
- 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别
- 如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样
- 可以单独给扩展添加的成员设置访问级别
- 不能给用于遵守协议的扩展显式设置扩展的访问级别
- 在同一文件中的扩展,可以写成类似多个部分的类型声明
- 在原本的声明中声明一个私有属性,可以在同一文件中的扩展中访问它
- 在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它
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()
}
}
将方法赋值给var、let
struct Person {
var age: Int
func run(_ v: Int) { print("func run",age, v)}
static func run(_ v: Int) { print("static func run" , v)}
}
// (Person) -> (Int) ->()
var fn1: (Person) -> (Int) ->() = Person.run
// (Int) ->()
var fn2 = fn1(Person(age: 10))
fn2(20) // func run 10 20
var fn3 = Person.run
fn3(30) // static func run 30
内存管理
- 跟 OC 一样,Swift 也是采用基于引用计数的 ARC 内存管理方案(针对堆空间)
- Swift 中的 ARC 有3种引用
- 强引用:默认情况下,引用都是强引用
- 弱引用:通过 weak 定义弱引用
- 必须是可选类型的 var,因为实例销毁后,ARC 会自动将弱引用设置为 nil
- ARC 自动给弱引用设置为 nil 时,不会触发属性观察器
- 无主引用:通过 unowned 定义无主引用
- 不会产生强引用,实例销毁后仍然存储着实例的内存地址(和 oc 的 unsafe_unretained 类似)
- 试图在实例销毁后访问无主引用,会产生运行时错误(野指针)
weak、unowned 的使用限制
- weak、unowned 只能用在类实例上面
Autoreleasepool
public func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result
循环引用(Reference Cycle)
- weak、unowned 都能解决循环引用的问题,unowned 要比 weak 少一些性能消耗
- 在生命周期可能会变为 nil 的使用 weak
- 初始化赋值后再也不会变为 nil 的使用 unowned
闭包的循环引用
- 闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行了retain操作)
- 可以在闭包表达式的捕获列表声明 weak 或 unowned 引用,解决循环引用问题
class Person {
var fn: (() -> ())?
func run() { print("run")}
deinit {
print("Person deinit")
}
}
func test() {
let p = Person()
// 下面的闭包导致p被循环引用,无法释放
// p.fn = {
// p.run()
// }
p.fn = {
[weak p] in
p?.run()
}
// 此时可以打印出 Person deinit p被释放了
}
test()
- 如果想在定义闭包属性的同时引用 self ,这个闭包必须是 lazy 的(因为在实例初始化完毕之后才能引用 self )
class Person {
var age: Int = 0
// fn内部如果用到了实例成员(属性、方法),编译器会强制要求明确写出self
lazy var fn: (() -> ()) = {
[weak self] in
self?.run()
}
// 如果lazy属性是闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生命周期就结束了)
lazy var getAge: Int = {
self.age
}()
func run() { print("run")}
deinit {
print("Person deinit")
}
}
@escaping
- 非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数
- 非逃逸闭包:闭包调用发生在函数结束前,闭包调用在函数作用域内
- 逃逸闭包: 闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过 @escaping 声明
import Dispatch
typealias Fn = () -> ()
// fn是非逃逸闭包
func test1(_ fn: Fn) { fn() }
var gFn: Fn?
// fn 是逃逸闭包
func test2( _ fn: @escaping Fn) { gFn = fn }
// fn是逃逸闭包
func test3(_ fn: @escaping Fn) {
// DispatchQueue.global().async 也是一个逃逸闭包
DispatchQueue.global().async {
fn()
}
}
- 逃逸闭包不能捕获 inout 参数
typealias Fn = () -> ()
// fn是非逃逸闭包
func test1(_ fn: Fn) { fn() }
func test2( _ fn: @escaping Fn) { fn() }
func test3(value: inout Int) {
test1 {
value += 1
}
// 逃逸闭包不能捕获
// 逃逸闭包不能确定调用时机,捕获inout参数会捕获其地址值,所有会出问题,不允许
// test2 {
// value + 1
// }
}
内存访问冲突
- 内存访问冲突会在两个访问满足以下条件时发生:
- 至少一个是写入操作
- 它们访问的是同一块内存
- 它们的访问时间重叠(比如在同一个函数内)
// 存在内存访问冲突
// 报错 打印 Simultaneous accesses to 0x100008020, but modification requires exclusive access.
var step = 1
func increment(_ num: inout Int) { num += step}
//increment(&step)
// 解决内存冲突
var copyOfStep = step
increment(©OfStep)
step = copyOfStep
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var tulpe = (health: 10, energy: 20)
// 下面还是会访问内存冲突,因为health,energy是在一块内存里的,是整体
balance(&tulpe.health, &tulpe.energy)
- 如果下面的条件可以满足,就说明重叠访问结构体的属性是安全的
- 你只访问实例存储属性,不是计算属性或者雷属性
- 结构体是局部变量而非全局变量
- 结构体要么没有被闭包捕获,要么只被非逃逸闭包捕获
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
// 放到函数里就不会报错了
func test() {
var tulpe = (health: 10, energy: 20)
balance(&tulpe.health, &tulpe.energy)
}
test()
指针
- Swift 中也有专门的指针类型,这些都被定性为 Unsafe (不安全的) 常见的有以下四种类型
UnsafePointer<Pointee>
类似于const Pointee *
UnsafeMutablePointer<Pointee>
类似于Pointee *
UnsafeRawPointer
类似于const void *
UnsafeMutableRawPointer
类似于`void *
var age = 10
//func test1(_ ptr: UnsafeMutablePointer<Int>) {
// ptr.pointee = 20
// print("test1",ptr.pointee)
//}
//func test2(_ ptr: UnsafePointer<Int>) {
// print("test2",ptr.pointee)
//}
func test3(_ ptr: UnsafeRawPointer) {
print("test3",ptr.load(as: Int.self))
}
func test4(_ ptr: UnsafeMutableRawPointer) {
ptr.storeBytes(of: 30, as: Int.self)
}
//test2(&age) // test2 10
//test1(&age) // test1 20
//print(age) // 20
test3(&age) // test3 10
test4(&age)
print(age) // 30
----------------------------------------------------------------
// 指针的应用示例 UnsafeMutablePointer<ObjCBool>
var arr = NSArray(objects: 11, 22, 33, 44)
// oc -> BOOL *
// swift -> UnsafeMutablePointer<ObjCBool>
arr.enumerateObjects { (element, idx, stop) in
print(idx, element)
if idx == 2 {
stop.pointee = true
}
print(idx, element)
}
// 更好的写法
for (idx, element) in arr.enumerated() {
print(idx, element)
if idx == 2 {
break
}
}
获得指向某个变量的指针
var age = 10
var ptr1 = withUnsafePointer(to: &age) { $0 }
var ptr2 = withUnsafeMutablePointer(to: &age) { $0 }
ptr2.pointee = 30
print(age) // 30
//@inlinable public func withUnsafeMutablePointer<T, Result>(to value: inout T, _ body: (UnsafeMutablePointer<T>) throws -> Result) rethrows -> Result {
// body(to)
//}
func wihtUnsafePointer<Result, T>(to: UnsafePointer<T>, body: (UnsafePointer<T>) -> Result) ->Result{
return body(to)
}
var ptr3 = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer($0) }
var ptr4 = withUnsafePointer(to: &age) { UnsafeRawPointer($0) }
ptr3.storeBytes(of: 33, as: Int.self)
print(ptr4.load(as: Int.self)) // 33
print(age) // 33
获得指向堆空间实例的指针
class Person {
var age: Int
init(age: Int) {
self.age = age
}
}
var person = Person(age: 21)
// ptr 存储的是 person 变量的地址值
var ptr = withUnsafePointer(to: &person) { UnsafeRawPointer($0) }
print(ptr) // 0x00000001000031e0
var address = ptr.load(as: UInt.self) // 堆空间person对象地址值
var ptr2 = UnsafeMutableRawPointer(bitPattern: address)
print(ptr2) // Optional(0x000000010056b350)
创建指针
UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
deallocate()
malloc(16)
free(ptr)
var ptr = malloc(16)
ptr?.storeBytes(of: 11, as: Int.self)
ptr?.storeBytes(of: 22, toByteOffset: 8, as: Int.self)
print(ptr?.load(as: Int.self))
print(ptr?.load(fromByteOffset: 8, as: Int.self))
free(ptr)
class Person {
var age: Int = 0
var name: String = ""
init(age: Int, name: String) {
self.age = age
self.name = name
}
deinit {
print("deinit")
}
}
var ptr = UnsafeMutablePointer<Person>.allocate(capacity: 1)
defer {
ptr.deinitialize(count: 1)
ptr.deallocate()
}
ptr.initialize(to: Person(age: 10, name: "json"))
print(ptr[0].age)
指针之间的转换
- unsafeBitCast 是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据
- 也就是说你内存中原来是什么,转换之后的还是什么
- 如果用 unsafeBitCast 强转,强转后的字节数不同,那么会报错
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
ptr.assumingMemoryBound(to: Int.self).pointee = 11
(ptr + 8).assumingMemoryBound(to: Double.self).pointee = 20.0
print(unsafeBitCast(ptr, to: UnsafePointer<Int>.self).pointee)
print(unsafeBitCast(ptr + 8, to: UnsafePointer<Double>.self).pointee)
var age = -1
var age1 = unsafeBitCast(age, to: UInt.self)
print(age, age1)
-----------------------执行结果-----------------------
(lldb) x/wg 0x7ffeefbff438
0x7ffeefbff438: 0xffffffffffffffff
(lldb) x/wg 0x7ffeefbff430
0x7ffeefbff430: 0xffffffffffffffff
-1 18446744073709551615