聊一聊,Swift的内存管理


   在Objective-C中无论ARC、MRC,开发中我们还是需要对内存进行管理,主要在于,避免循环引用、代理、KVO移除、Block这些,再有就是属性声明时的语义(property_attribute)。关于Objective-C中的引用计数制我就不重复了。

   可到了Swift中,对于属性的声明就只剩下了lazy\weak。这个可让这些从Objective-C走来的程序员们一下就犯了难。

   其实并非如此,Swift中依然需要所有权声明,请大家一定要认真阅读下边的内容。


聊一聊,Swift的内存管理

1、自动引用计数的工作机制


   当你每次创建一个类的新的实例的时候,ARC 会分配一大块内存用来储存实例的信息。内存中会包含实例的类型信息,以及这个实例所有相关属性的值。

   此外,当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。

   然而,当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,如果你试图访问这个实例,你的应用程序很可能会崩溃。

   为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。

   为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。


2、举个栗子


   接下来的代码片段定义了三个类型为Person?的变量,用来按照代码片段中的顺序,为新的Person实例建立多个引用。由于这些变量是被定义为可选类型(Person?,而不是Person),它们的值会被自动初始化为nil,目前还不会引用到Person类的实例。


var reference1: Person?
var reference2: Person?

var reference3: Person?


现在你可以创建Person类的新实例,并且将它赋值给三个变量中的一个:

reference1 = Person(name: "John Appleseed")

// prints "John Appleseed is being initialized”


   应当注意到当你调用Person类的构造函数的时候,“John Appleseed is being initialized”会被打印出来。由此可以确定构造函数被执行。

   由于Person类的新实例被赋值给了reference1变量,所以reference1到Person类的新实例之间建立了一个强引用。正是因为这一个强引用,ARC 会保证Person实例被保持在内存中不被销毁。

如果你将同一个Person实例也赋值给其他两个变量,该实例又会多出两个强引用:

reference2 = reference1

reference3 = reference1


   现在这一个Person实例已经有三个强引用了。

   如果你通过给其中两个变量赋值nil的方式断开两个强引用(包括最先的那个强引用),只留下一个强引用,Person实例不会被销毁:


reference1 = nil

reference2 = nil


   在你清楚地表明不再使用这个Person实例时,即第三个也就是最后一个强引用被断开时,ARC 会销毁它:


reference3 = nil

// 打印 “John Appleseed is being deinitialized”


3、类实例之间的循环强引用


   我们可能会写出一个类实例的强引用数永远不能变成0的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。

   你可以通过定义类之间的关系为weak弱引用或unowned无主引用,以替代强引用,从而解决循环强引用的问题。


class Apartment {
   
let unit: String
   
init(unit: String) { self.unit = unit }
   
weak var tenant: Person?
   
deinit { print("Apartment \(unit) is being deinitialized") }
}
class CreditCard {
   
let number: UInt64
   
unowned let customer: Customer
   
init(number: UInt64, customer: Customer) {
       
self.number = number
       
self.customer = customer
    }
   
deinit { print("Card #\(number) is being deinitialized") }

}


4、创建一个自动释放池(Autorelease Pool)


   在 Objective – C 框架链接的应用程序,通常在它们的每一个线程必须创建至少 一个自动释放池。如果应用程序使用管理模型,即应用程序处理的 retain 和 release 对象,那么自动释放池捕获任何从该线程 autorelease 的对象。在Swift框架下呢??最近在Spritekit下做了一个Swift的demo。然后为了加载时高效一点,在子线程中加载背景,结果发现内存永远的增涨,以0.2MB,0.2MB的固定速度增涨,


http://www.code4app.com/ios/Swift%E7%9A%84%E5%AE%87%E5%AE%99%E6%88%98%E6%9C%BA%E6%B8%B8%E6%88%8FSpriteKit/569d055ab5ad2eba268b47f9


   排查最后,发现刚好背景图片切片的大小是0.2MB。这样的话,想来是没有别的原因了。我已经按照,正常的逻辑,在画面离开屏幕时就删除节点了,但是内存仍然在增加。想来也是没有其他原因。 在Swift中,子线程的内存仍然需要添加自动释放池,结果Swift开发指南中,并没有提到。

   不过还是在Swifter中找到了,我想要的。

在 Swift 中我们也是能使用 autoreleasepool 的 — 虽然语法上略有不同。相比于原来在 Objective-C 中的关键字,现在它变成了一个接受闭包的方法:


func autoreleasepool(code: () -> ())


   利用尾随闭包的写法,很容易就能在 Swift 中加入一个类似的自动释放池了:


func loadBigData() {
   
if let path = NSBundle.mainBundle()
        .pathForResource(
"big", ofType: "jpg") {
           
           
for i in 1...10000 {
                autoreleasepool {
                   
let data = NSData.dataWithContentsOfFile(
                        path, options:
nil, error: nil)
                   
                    NSThread.sleepForTimeInterval(0.5)
                }
            }
    }

} 


   这样改动以后。果然程序变得舒服多了。

      经过ARC和Swift的双重隐藏,内存管理已经越来越不显眼了,但是其中的坑依然不少,希望大家通过本文,能加深对Swift的理解。

长按下方二维码关注最新推送的技术博客!