阅读 214

Swift的ARC和内存泄漏

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上的观察者,网络调用还是其他异步任务,都要记得在内存泄漏之前之前检查一下。 PS:

最近加了一些iOS开发相关的QQ群和微信群,但是感觉都比较水,里面对于技术的讨论比较少,所以自己建了一个iOS开发进阶讨论群,欢迎对技术有热情的同学扫码加入,加入以后你可以得到:

  1. 技术方案的讨论,会有在大厂工作的高级开发工程师尽可能抽出时间给大家解答问题

  2. 每周定期会写一些文章,并且转发到群里,大家一起讨论,也鼓励加入的同学积极得写技术文章,提升自己的技术

  3. 如果有想进大厂的同学,里面的高级开发工程师也可以给大家内推,并且针对性得给出一些面试建议

群已经满100人了,想要加群的小伙伴们可以扫码加这个微信,备注:“加群+昵称”,拉你进群,谢谢了

关注下面的标签,发现更多相似文章
评论