12/08/2018, 15:01

Reference Cycle for Closures

Một reference cycle xảy ra khi một đối tượng A có strong reference đến đối tượng B và ngược lại. Điều này không chỉ giữa các class với nhau mà còn giữa class và closure. Chúng ta thường thấy code với [weak self] hoặc [unowned self] xuất hiện trong closure, nhưng nó có cần thiết hay không? Liệu ...

Một reference cycle xảy ra khi một đối tượng A có strong reference đến đối tượng B và ngược lại. Điều này không chỉ giữa các class với nhau mà còn giữa class và closure. Chúng ta thường thấy code với [weak self] hoặc [unowned self] xuất hiện trong closure, nhưng nó có cần thiết hay không? Liệu chúng ta có phải luôn luôn sử dụng unowned/weak trong closure hay không? Đây là câu hỏi mà chúng ta sẽ trả lời trong bài viết này

Một reference cycle sẽ xảy ra nếu và chỉ nếu:

  1. Class instance có một strong reference đến closure
  2. Closure có một strong reference đến class Ở 2 trường hợp trên thì nếu ta thay thế bằng một weak reference thì bạn sẽ khắc phục được vấn đề Chúng ta sẽ xem xét kỹ hơn ở 2 trường hợp dưới đây.

Đây là một trường hợp điển hình khi class instance có strong reference đến closure một cách trực tiếp hoặc gián tiếp. Gián tiếp ở đây có nghĩa là instance giữ một reference thông qua một đối tượng thứ ba. Chúng ta hãy cùng xem ví dụ sau:

// Now, myStrongClass has strong reference to the closure
class MyStrongClass {
    deinit { print("deinit MyStrongClass") }
    var theClosure: (()->Void)?
    
    func runClosure() {
        theClosure = {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(3 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
                print("Running the closure and this is self: (self)")
            }
        }
        theClosure!()
    }
}

var myStrongClass: MyStrongClass? = MyStrongClass()
myStrongClass?.runClosure()
myStrongClass = nil

Trong đoạn code ở trên, myStrongClass sẽ không deinit ngay cả khi ta gán giá trị cho nó bằng nil. Đó là vì closure đang có một strong reference đến myStrongClass. Giải pháp ở đây là gán self với unowned hoặc weak.

theClosure = { [weak self] in
  ...
}

Đây là ví dụ cho thấy bạn cần phải sử dụng weak/ owned, nếu không thì sẽ phát sinh memory leak.

Trong trường hợp này, bạn có thể nghĩ rằng closure được khai báo với weak (trong khi vẫn giữ strong reference đến self)

weak var theClosure: (()->Void)?

Nhưng việc đó là không thể. weak chỉ có thể được áp dụng cho class hoặc class-bound protocol. Để minh hoạ cho trường hợp này, chúng ta sẽ sử dụng closure trong một local scope (do đó không strong reference đến self)

class MyHolder {
    var value = "initial"
    deinit { print("deinit MyHolder") }
    
    func runClosure() {
        let strongClosure: (()->Void) = {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(3 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
                print("Value of MyHolder: (self.value)")
            }
        }
        strongClosure()
    }
}

var holder: MyHolder? = MyHolder()
holder?.value = "changed"
holder?.runClosure()       // Prints changed then deinit
holder = nil

// Result: holder will deinit once the closure has completed (and printed the value)
// closure deinit, therefore instance deinit too

Closure không thể reference đến đối tượng. Nó chỉ sử dụng trong phạm vi của hàm runClosure. strongClosure có strong reference đến self. Đó là lý do mà nó sẽ in kết quả trước, sau đó self mới được released. Điểm nhấn mạnh ở dây là: Nếu self không có strong reference đến closure thì closure có thể strong reference đến self.

Ví dụ thứ (2) xảy ra phổ biến hơn khi ta sử dụng singleton

class Singleton {
    static let sharedInstance = Singleton()    
    func runClosure(closure: (()->Void)) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(3 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
            closure()
        }
    }
}

class MyHolder {
    ...
    func runClosure() {
        Singleton.sharedInstance.runClosure { 
            print("Value of MyHolderWithSingleton: (self.value)")
        }
    }
}

Đoạn code trên tương tự như ở ví dụ (2) - closure có một strong reference đến self, nhưng self không có strong reference dến closure và cả singleton. Chúng ta thường thấy code gán self là weak như thế này:

func runClosure() {
    Singleton.sharedInstance.runClosure { [weak self] in
        print("Value of MyHolderWithSingleton: (self?.value)")
    }
}

Thông thường thì self là một view controller, với đoạn code trên, ta thấy closure reference weak đến view controller. Nhưng self lại không có strong reference đến closure nên không thể xảy ra reference cycle . Một lần nữa khẳng định: Nếu self không có strong reference đến closure thì closure có thể strong reference đến self.

0