ARC
Swift引入了一项称为自动引用计数(ARC)的强大功能,可以处理应用程序的大部分内存管理。然而,初学者程序员通常不知道它是如何运作的。
ARC负责分配和释放对象使用的内存。为了使其自动化,ARC计算对您创建的变量的引用。
class Object {
var name: String
}
let object = Object() //ARC object reference count: 1
var reference2 = object //ARC object reference count: 2
var reference3 = reference1 //ARC object reference count: 3
var reference4 = object //ARC object reference count: 4
复制代码
删除对象的引用后,ARC将自动为您释放对象。
let object = null //ARC object reference count: 3
var reference2 = null //ARC object reference count: 2
var reference3 = null //ARC object reference count: 1
var reference4 = null //ARC object reference count: 0
复制代码
当ARC释放内存时,将调用对象的deinit() 方法。
内存泄漏
在Swift中,内存泄漏存在与循环引用中。当两个对象彼此保持强引用时,会发生循环引用。
这通常发生在逃逸闭包中。
在使用逃逸闭包写回调时,任何使用任何当前类的方法或者self都将创建对该实例的强引用,从而造成循环引用,因为闭包将保持对类的强引用,并且类将保留对闭包所在类的强引用。
下面的示例显示了使用逃逸闭包时的常见的循环引用。
class NetworkHelper {
func getFeed(completion: @escaping ([FeedItem]) -> Void) {
Alamofire.request(…).responseJSON { (response) in
if let value = response.result.value {
if let json = JSON(value)[Constants.items].array {
completion(json.flatMap(FeedItem.init))
}
}
}
}
}
class FeedViewController {
var tableView: UITableViewController
var feedItems: [FeedItem]
var networkHelper: NetworkHelper
override func viewDidLoad() {
...
networkHelper.getFeed() { items in
self.feedItems = items
self.tableView.reloadData()
}
}
}
复制代码
在上面的示例中,FeedViewController通过变量networkHelper保存对NetworkHelper的强引用。然后,对FeedViewController的引用作为闭包传递给networkHelper,从而创建从networkHelper到FeedViewController的强引用。
即使没有其他对这些对象的引用,ARC也永远无法清理FeedViewController或NetworkHelper。这将导致两个对象存在,直到用户关闭应用程序。如果重新创建FeedViewController,则可能再次发生循环引用,从而使问题更加严重。弱引用和无主引用
要中断循环引用,必须从传递给networkHelper的闭包中删除对FeedViewController的强引用。这是通过使用弱或无主的自我来完成的。
weak关键字通过不递增ARC的引用计数来创建对变量的弱引用。但是,由于它不是强引用,因此无法保证在执行闭包时对象将存在。因此,只要您使用weak关键字,该变量就是可选的。
unowned关键字也不会增加ARC的引用计数,但它也不是可选的。但是由于它没有对变量的强引用,它可能不存在并且可能完全指向其他东西。除非两个对象始终像计算机及其处理器一样存在,否则不应使用unowned。
要修复上面的示例,您只需指定self是弱引用,保留周期将被破坏。
class FeedViewController {
var tableView: UITableViewController
var feedItems: [FeedItem]
var networkHelper: NetworkHelper
override func viewDidLoad() {
...
networkHelper.getFeed() { [weak self] items in
self?.feedItems = items
self?.tableView.reloadData()
}
}
}
复制代码
调试内存管理
XCode具有很好的功能,可以检查应用程序运行时存在的内存使用情况,引用和对象实例。
Xcode Memory Graph暂停您的应用程序执行并显示当前存在的所有对象。您还可以选择对象的实例,并查看哪些对象包含对它的引用。当内存泄漏确实发生时,Xcode甚至经常用紫色的解释点突出显示有问题的类。添加一个简单的保留周期并多次执行该操作会产生如下所示的内存图。
单击其中一个实例进一步显示闭包持有对DetailsViewController的引用,我在其中创建了以下循环引用。let retainCycle = RetainCycle()
override func viewDidLoad() {
retainCycle.keepMe {
self.view.backgroundColor = .white
}
}
class RetainCycle {
func keepMe(closure: @escaping () -> Void) {
URLSession.shared.dataTask(...) { (data, _, _) in
closure()
}
}
}
复制代码
因此,下次创建闭包时,无论是NotificationCenter上的观察者,网络调用还是其他异步任务,都要记得在内存泄漏之前之前检查一下。