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将自动为您释放对象。json

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() 方法。api


内存泄漏

在Swift中,内存泄漏存在与循环引用中。当两个对象彼此保持强引用时,会发生循环引用。bash

这一般发生在逃逸闭包中。微信

在使用逃逸闭包写回调时,任何使用任何当前类的方法或者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的强引用。这是经过使用弱或无主的自我来完成的。ide

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人了,想要加群的小伙伴们能够扫码加这个微信,备注:“加群+昵称”,拉你进群,谢谢了

相关文章
相关标签/搜索