Tản mạn về [weak self]

Từ hồi vào project swift vào năm 2018 thì thấy anh em dân dev rất hay dùng bừa một tính năng nào đó để fix(kiểu fix triệu chứng) mà không hiểu rõ tại sao lại dùng như vậy. Và còn có một anh không nắm rõ quản lý bộ nhớ nên rất sợ vấn đề retain cycle trong iOS. Vậy nên nay mình muốn viết một bài chia sẻ về vấn đề sử dụng weak self bừa bãi trong swift của dân dev iOS.

Nội dung bài viết tập trung vào vấn đề sử dụng weak self để tránh retain cycle. Một số trường hợp sử dụng nhằm mục đích khác không đề cập trong bài viết này.

Retain cycle, vấn đề đau đầu của các lập trình viên iOS

Automatic Reference Counting

Trong ARC có 2 kiểu property là:

  • Tăng retain count: strong
  • Không tăng retain count: weak, unowned

Retain Cycles là trường hợp hai hay nhiều object tham chiếu theo kiểu retain lẫn nhau tạo thành một vòng tròn. Nó sẽ gây ra hiện tại các object đó không được huỷ cho dù đã không còn sử dụng nữa.

Về cơ bản thì việc sử dụng [weak self] bừa bãi của các thanh niên chủ yếu để tránh retain cycle nói trên. Vì là dùng bừa bãi, nên là một số chỗ không cần thì lại sử dụng :D. Đến đây chắc 1 số thanh niên sẽ cảm thấy nhồn nhột =)). Trước hết chúng ta sẽ tìm hiểu tại sao lại cần dùng [weak self].

Mình thấy các thanh niên hay dùng [weak self] khi sử dụng closure để tránh retain self ở trong closure. Mục đích của việc đó cũng là để tránh retain cycle, tuy nhiên nhiều trường hợp không tạo thành retain cycle nhưng nhiều người vẫn cố dùng mà không hiểu tại sao phải dùng, giống như là dùng trong vô thức vậy. @@

DispatchQueue.main.async {[weak self] in
	self?.reloadData()    
}

Khi sử dụng closure, swift compiler mặc định sẽ retain các object nằm bên ngoài closure để nhằm mục địch sử dụng sau này( khi closure được gọi). Nên ta sử dụng [weak self] để closure không retain lại self. Tuy nhiên không phải trường hợp nào cũng sử dụng, bởi vì những trường hợp đó thì sự retain này không gây ra retain cycle.

Vậy ta sử dụng [weak self] trong trường hợp nào để tránh retain cycle.

  • Closure được lưu lại 1 property của self.
  • Closure được lưu lại bởi 1 object khác và object đó được lưu tại strong property của self.

Ngoài 2 trường hợp trên thì ta không nên sử dụng weak self vì nó khá là thừa.

Ví dụ 1 số trường hợp không dùng [weak self]

Khi gọi self trong closure của DispatchQueue.main.async thì retain count của self sẽ tăng lên thêm 1 và sẽ được giảm xuống khi closure này được chạy xong(bị huỷ).

DispatchQueue.main.async { in
	self.reloadData()    
}

Tương tự, khi gọi self trong closure của map thì retain count của self sẽ tăng lên thêm 1 và sẽ được giảm xuống khi closure này được chạy xong(bị huỷ).

let array = [1, 2, 3]
array.map { item in
	return self.mapItemIndex(item)
}

Ví dụ trường hợp dùng weak self do tạo thành retain cycle

class A {
    var callback: ((String) -> Void)!
    
    func run() {
        self.callback("Demo value")
    }
}

class B {
    var instanceOfA: A = A()
    
    func setup() {
        instanceOfA.callback = {[weak self] value in
            self?.doPrint(val: value)
        }
    }
    
    func run() {
        instanceOfA.run()
    }
    
    func doPrint(val: String) {
        print("Value: \(val)")
    }
}

let b = B()
b.setup()
b.run()
Khi không dùng [weak self], thì khi biến b không được dùng nữa, thì nó sẽ có retain cycle là 1, instanceOfA là 1 và callback cũng là 1. Dẫn đến cả 3 không được giải phóng.
Trong trường hợp sử dụng [weak self]. Khi biến b không được dùng nữa, thì nó sẽ có retain cycle là 0, dẫn tới giải phóng instanceOfA sau đó sẽ giải phóng callback. Không có object nào bị leak trong trường hợp này.

Có thắc mắc gì các bạn cứ để lại comment bên dưới nhé. 😀

Leave a Reply