×
广告

【Swift概念】Swift的内存管理

96
devCK凯
2016.01.20 18:47* 字数 1100

Swift的内存管理怎么办??

在Objective-C中无论ARC、MRC,开发中我们还是需要对内存进行管理,主要在于,避免循环引用、代理、KVO移除、Block这些,再有就是属性声明时的语义(property_attribute)。关于Objective-C中的引用计数制我就不重复了。
可到了Swift中,对于属性的声明就只剩下了lazy\weak。这个可让这些从Objective-C走来的程序员们一下就犯了难。

一、聊一聊,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无主引用,以替代强引用,从而解决循环强引用的问题。

 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://code4app.com/ios/569d055ab5ad2eba268b47f9](http://code4app.com/ios/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)
    }
   }
  }
}

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