一篇文章看懂自动引用计数和循环引用到底是怎么回事

ARC

Swift中的ARC(Automatic Reference Counting,自动引用计数)是一种内存管理机制,用于跟踪和管理应用程序中的对象引用计数,确保内存中的对象只在需要的时候存在,当对象的引用计数为0时就会被系统析构掉。

ARC的重要概念

  1. 引用计数:每个对象都有一个引用计数,表示有多少个指针引用该对象。当引用计数为0时,对象将被释放。
  2. 强引用:默认情况下,Swift中的对象之间都是强引用关系,即一个对象持有另一个对象的强引用,如果持有该引用的对象被释放了,它所持有的对象也将被释放。
  3. 弱引用:弱引用不会增加对象的引用计数。当持有该引用的对象被释放时,弱引用会被自动设置为nil。在Swift中,使用weak关键字来定义弱引用。
  4. 无主引用:与弱引用类似,无主引用也不会增加对象的引用计数。但不同之处在于,无主引用默认情况下不是可选类型,因此必须始终指向一个对象。在Swift中,使用unowned关键字来定义无主引用。
  5. 循环引用:如果两个对象互相持有对方的强引用,将会导致循环引用,即这两个对象都无法被释放。为了避免循环引用,可以使用弱引用或无主引用。

造成循环引用的情况以及解决办法

两种解决办法:

1. 使用弱引用

在以下示例中,类A持有一个强引用类B,类B持有一个弱引用类A。这可以防止循环引用,因为类B不能保持类A的引用计数。当类A释放时,类B的弱引用将自动设置为nil。若没有weak,objA对象的b变量指向objB对象,objB对象的a变量指向objA对象,且都是强引用,这时便造成了循环引用,内存泄漏。

class A {
    var b: B?
}

class B {
    weak var a: A?
}

var objA: A? = A()
var objB: B? = B()

objA?.b = objB
objB?.a = objA

objA = nil
objB = nil

2. 使用无主引用

在以下示例中,类A持有一个强引用类B,类B持有一个无主引用类A。这可以防止循环引用,因为类B不能保持类A的引用计数。当类A释放时,类B的无主引用将继续指向已释放的对象,这可能会导致运行时错误。因此,需要确保类B在访问类A时检查其无主引用是否为nil。unowned的用法和weak类似,区别在于无主引用是对非可选型变量,而弱引用是对可选型变量。

class A {
    var b: B?
}

class B {
    unowned var a: A
    
    init(a: A) {
        self.a = a
    }
}

var objA: A? = A()
var objB: B? = B(a: objA!)

objA?.b = objB
objB = nil

objA = nil

三种循环引用情况

1. 闭包捕获self

在闭包中捕获self可能会导致循环引用。当闭包中捕获self时,它会持有对self的强引用,这可能会导致循环引用。

解决办法是使用weak或unowned来定义捕获的引用。如果闭包可能在self释放之后被调用,应该使用unowned,否则应该使用weak。

class MyClass {
    var closure: (() -> Void)?
    func someMethod() {
        closure = { [weak self] in
            self?.someOtherMethod()
        }
    }
    func someOtherMethod() {
        // ...
    }
}

2. 委托和代理

委托和代理模式可能会导致循环引用。在这个例子中,我们定义了一个协议MyDelegate,MyClass是遵循这个协议的类。同时,我们定义了一个UIViewController的子类MyViewController,它是MyClass的代理对象。在MyViewController中,我们将myClass的代理设置为self,并实现了MyDelegate协议中的方法didSomething()。在MyViewController中,当代理方法被调用时,我们调用了myClass的doSomething()方法。

这个例子中的循环引用问题是,MyViewController持有对myClass的强引用,而myClass持有对MyViewController代理对象的强引用。这意味着,当MyViewController被销毁时,它并不能被释放,因为MyClass仍然持有对它的强引用。而当MyClass被销毁时,它也不能被释放,因为MyViewController仍然持有对它的强引用。这样就形成了循环引用的问题。

为了解决这个问题,我们需要使用weak来声明对代理对象的引用。

 

protocol MyDelegate: class {
    func didSomething()
}

class MyClass {
    weak var delegate: MyDelegate?

    func doSomething() {
        // ...
        delegate?.didSomething()
        // ...
    }
}

class MyViewController: UIViewController, MyDelegate {
    let myClass = MyClass()

    override func viewDidLoad() {
        super.viewDidLoad()

        myClass.delegate = self
    }

    deinit {
        print("MyViewController deinit")
    }

    func didSomething() {
        // ...
        myClass.doSomething()
        // ...
    }
}

3. 对象间相互引用

如果两个对象相互持有对方的强引用,那么就会发生循环引用。

解决办法是将其中一个引用定义为弱引用或无主引用。如果两个对象的生命周期相同,则应该使用弱引用,否则应该使用无主引用。

class MyClass {
    var otherObject: OtherClass?
    // ...
}

class OtherClass {
    weak var myObject: MyClass?
    // ...
}

你可能感兴趣的:(ios,swift,ios,开发语言)