内存管理
Swift
同OC
一样,也是基于引用计数的ARC
内存管理方案(堆空间), Swift中的ARC有3种引用:
- 强引用(strong reference)
- 默认情况下,引用都是强引用
- 弱引用(weak reference)
- 通过
weak
定义弱引用; 必须是可选类型的var
,因为实例销毁后,ARC会自动将弱引用置为nil
(ARC自动给弱引用设置nil
时不会触发属性观察器
)
- 通过
- 无主引用(unowned reference)
- 通过
unowned
定义无主引用, 不会产生强引用
,实例销毁后仍然存储着实例的内存地址(类似于OC里的unsafe_unretained
), 如果试图在实例销毁后访问无主引用,会产生运行时错误(野指针)
- 通过
weak、unowned的使用限制
weak、unowned
只能用在类实例上面
// 协议写上AnyObject的,只能被类遵守
protocol Livable : AnyObject {}
class Person {
}
weak var p0: Person?
weak var p1: AnyObject?
weak var p2: Livable? // 协议写上AnyObject的,只能被类遵守,因此该协议也可以使用weak修饰
unowned var p10: Person?
unowned var p11: AnyObject?
unowned var p12: Livable?
自动释放池(AutoreleasePool)
自动释放池
在swift
中的定义为:
public func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result
使用方式:
autoreleasepool {
let person = Person(age:20, name:"jack")
person.run()
}
循环引用(Reference Cycle)
weak
、unowned
都能解决循环引用的问题,但unowned
要比weak
少一些性能消耗(weak在实例销毁后要设置实例为nil)
在生命周期中可能会变为nil
的建议使用weak
初始化赋值之后再也不会变为nil
的建议使用unowned
闭包的循环引用
闭包表达式
默认会对用到的外层对象
产生额外的强引用
(对外层对象进行retain操作)
class Person {
// 定义fn属性
var fn: (() -> ())?
func run() {
print("run")
}
deinit {
print("析构函数执行了")
}
}
func test() {
let p = Person()
p.fn = {
// 此闭包对外面的p产生了强引用
p.run()
}
}
// 执行test方法后,出作用域后并没有执行析构函数
test()
解决方案就是在闭包表达式的捕获列表声明weak
或unowned
引用,解决循环引用问题
class Person {
// 定义fn属性
var fn: ((Int) -> ())?
func run() {
print("run")
}
deinit {
print("析构函数执行了")
}
}
func test() {
let p = Person()
p.fn = {
// 在闭包表达式的捕获列表声明weak/unowned引用,解决循环引用问题
// [weak p]代表捕获列表
// (age)代表参数列表
// in后面的是函数体
[weak p](age) in
p?.run() // 弱引用必须是可选类型,因此调用时加问号
// 使用unowned的方式为
// [unowned p] in
// p.run()
}
}
// 执行test方法后,出作用域后执行了析构函数
test()
捕获列表
还可以定义新的名称
p.fn = {
[weak wp = p, unowned up = p, a = 10 + 20](age) in
wp?.run()
}
如果想在定义闭包属性的同时引用self
,这个闭包必须是lazy
的(因为在实例初始化完毕之后才能引用self
)
class Person {
// 定义闭包表达式fn
lazy var fn: (() -> ()) = {
// 由于self在实例初始化完成之后才能使用,所以直接调用编译会报错
// 解决的方法是使用lazy修饰闭包
// 如果访问到了self变量,在没有调用fn闭包的时候是没问题的,一旦调用了fn闭包,同样会产生循环引用
// 解决的方式就是使用weak或者unowned修饰
[weak p = self] in
p?.run()
}
func run() {
}
deinit {
print("析构函数执行了")
}
}
var p = Person()
p.fn()
@escaping
闭包分为逃逸闭包
、非逃逸闭包
,一般都是当做参数传递给函数
-
非逃逸闭包:闭包调用发生在函数结束前,闭包调用在函数作用域内
// 非逃逸闭包 func test(_ fn: () -> ()) { } test { print(1) }
-
逃逸闭包:闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过
@escaping
声明import Dispatch typealias Fn = () -> () // fn是逃逸闭包 var gFn: Fn? func test2(_ fn: @escaping Fn) {gFn = fn} // fn是逃逸闭包 func test3(_ fn: @escaping Fn) { DispatchQueue.global().async { fn() } }
逃逸闭包
不能捕获inout
参数
typealias Fn = () -> ()
func other1(_ fn: Fn) {
fn()
}
func other2(_ fn: @escaping Fn) {
fn()
}
func test(value: inout Int) -> Fn {
other1 {
value += 1
}
// error: 逃逸闭包不能捕获inout参数
func plus() {
value += 1
}
// error: 逃逸闭包不能捕获inout参数
return plus
}
内存访问冲突(Conflicting Access to Memory)
内存访问冲突
会在两个访问满足下列条件时发生:
- 至少一个是写入操作
- 它们访问的是同一块内存
- 它们访问时间重叠(比如在同一个函数内)
// 不存在内存访问冲突
func plus(_ num: inout Int) -> Int {
num + 1
}
var number = 1
number = plus(&number)
// 存在内存访问冲突
// 运行报错:Thread 1: Simultaneous accesses to 0x10001df40, but modification requires exclusive access
var step = 1
func increment(_ num: inout Int) {
num += step
}
increment(&step)
// ------解决内存访问冲突--------
var copyOfStep = step
increment(©OfStep)
step = copyOfStep
如果下面的条件可以满足,就说明重叠访问结构体的属性是线程安全的
- 只访问实例存储属性,不是计算属性或者类属性
- 结构体是局部变量而非全局变量
- 结构体要么没有被闭包捕获,要么只被非逃逸闭包捕获
指针
swift
钟也有专门的指针类型,这些都被定性为Unsafe(不安全的)
, 常见的有以下四种:
- UnsafePointer
(类似于`const Pointee *`) - UnsafeMutablePointer
(类似于`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)
}
test1(&age) // 输出test1: 20
test2(&age) // 输出test2: 20
print(age) // 输出20
test3(&age) // 输出test3: 20
test4(&age)
print(age) // 输出30
在遍历数组元素时,可使用指针设置条件:
var arr = NSArray(objects: 11,22,33,44)
arr.enumerateObjects { (element, idx, stop) in
if idx == 2 {
stop.pointee = true
}
}
获得指向某个变量的指针
var age = 11
var ptr1 = withUnsafeMutablePointer(to: &age) { $0 }
var ptr2 = withUnsafePointer(to: &age) { $0 }
ptr1.pointee = 22
print(ptr2.pointee) // 22
print(age) // 22
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 person = Person()
var ptr = withUnsafePointer(to: &person) { UnsafeRawPointer($0) }
var heapPtr = UnsafeRawPointer(bitPattern: ptr.load(as: UInt.self))
print(heapPtr!) // 0x00000001005342d0
创建指针
// 创建
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))!) // 11
print((ptr?.load(fromByteOffset: 8, as: Int.self))!) // 22
// 销毁
free(ptr)
或者这样
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
ptr.storeBytes(of: 11, as: Int.self)
ptr.advanced(by: 8).storeBytes(of: 22, as: Int.self)
print(ptr.load(as: Int.self)) // 11
print(ptr.advanced(by: 8).load(as: Int.self)) // 22
ptr.deallocate()
复杂类型指针创建
class Person {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
deinit {
print(name,"deinit")
}
}
// 创建一个容量为3的指针
var ptr = UnsafeMutablePointer<Person>.allocate(capacity: 3)
ptr.initialize(to: Person(age: 20, name: "jack"))
// ptr+1代表是指针空间的下一个(因为创建了有3个容量)
(ptr + 1).initialize(to: Person(age: 21, name: "rose"))
(ptr + 2).initialize(to: Person(age: 22, name: "kate"))
// 也可以通过下标去访问
ptr[2].name = "kate1"
// 销毁
ptr.deinitialize(count: 3)
指针之间的切换
可通过关键字assumingMemoryBound
将指针类型转换
// UnsafeMutableRawPointer类型指针
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
// 指针转换
ptr.assumingMemoryBound(to: Int.self).pointee = 11
(ptr + 8).assumingMemoryBound(to: Double.self).pointee = 22.0
print(unsafeBitCast(ptr, to: UnsafePointer<Int>.self).pointee) // 11
print(unsafeBitCast(ptr + 8, to: UnsafePointer<Double>.self).pointee) // 22.0
ptr.deallocate()
unsafeBitCast
是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据
类似于c++中的reinterpret_cast
本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.