+5

Tìm hiểu về Strong, Weak và Unowned với Swift

Đằng sau việc chúng ta code, chúng ta có khi nào để ý rằng tham chiếu đến những biến là các cụm từ strong weak, hoặc unowned khi viết code. Vậy ý nghĩa của chúng thực sự là gì ?

Việc sử dụng strong weak, hoặc unowned liên quan đến việc quản lý bộ nhớ trong Swift gọi là Automatic Reference Counting (ARC) . Định nghĩa trong Khoa học máy tính thì Reference Counting là kĩ thuật lưu lại số tham chiếu, con trỏ, hoặc sử lý liên quan đến resource như là đối tương, block hoặc bộ nhớ. Nói ngắn gọn, ARC giúp bạn lưu các tham chiếu vào trong bộ nhớ và giúp giải thoát nó đi khi không được dùng nữa.

Việc quản lý bộ nhớ đóng vai trò quan trọng việc phân vùng bộ nhớ để chương trình có thể chạy những yêu cầu của người sử dụng và có thể tái sử dụng lại vùng nhớ khi nó không cần thiết nữa.

ARC hoạt động như thế nào

Mỗi lúc bạn tạo một instance từ class qua hàm init() , ARC tự động cấp một vùng nhớ để lưu thông tin. Vùng nhớ đó lưu trữ instance đó cùng với các giá trị của thuộc tính của nó. Khi mà instance đó không còn cần thiết nữa, deinit() sẽ được gọi và ARC sẽ giải phóng vùng nhớ đó của instance.

Đoạn code sau đây sẽ demo về cách hoạt động trên

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    var gadget: Gadget?
    deinit {
        print("\(name) is being deinitialized")
    }
}
 
class Gadget {
    let model: String
    init(model: String) {
        self.model = model
        print("\(model) is being initialized")
    }
    var owner: Person?
    deinit {
        print("\(model) is being deinitialized")
    }
}

Sự khác biệt giữa Strong, Weak và Unowed

Bức ảnh dưới đây sẽ giải thích về yêu cầu về việc sử dụng của mỗi loại tham chiếu trên

  1. Thông thường, khi một thuộc tính được tạo ra, thuộc tính đó là strong trừ phi chúng ta muốn nó là weak hoặc unowned
  2. Khi thuộc tính là weak, nó sẽ không tăng việc đếm tham chiếu lên.
  3. Kiểu unowned nằm ở giữa, nó không phải là strong và cũng không phải là optional . Trình biên dịch sẽ hiểu là đối tượng đó sẽ không bị giải phóng khi mà những đối tượng tham chiếu đến nó vẫn còn trong bộ nhớ.

Kiểu tham chiếu : strong

Hãy nhìn vào ví dụ sau. Chúng ta có một biến của class Person với đối tượng là "kelvin" và class "Gadget" với đôí tượng là "iphone"

var kelvin: Person?
var iphone: Gadget?
 
kelvin = Person(name: "Kelvin")
iphone = Gadget(model: "iPhone 8 Plus")

Bây giờ nếu như bạn đặt cả hai biến này thành nil và chạy Playgrounds :

Hãy nhìn vào phần console bạn sẽ thấy cả hai biến đã được giải phóng cả rồi. Giờ chúng ta sẽ đặt owner của kelviniphone cho nhau. Hãy đặt đoạn code này trước khi set hai biến trên thành nil:

kelvin!.gadget = iphone
iphone!.owner = kelvin

Đây là một ví dụ để diễn tả việc liên kết chặt giữa các biến là như thế nào. Hãy nhớ rằng trong class Person có một biến của gadget mà ở đây nó chứa giá trị của iphone. Để nhìn một cách trực quan hơn thì nó sẽ như sau :

Giờ hãy chạy lại đoạn code trên, bạn sẽ thấy rằng chỉ có việc khởi tạo là thành công.

Vậy lý do vì sao, chúng ta đã gia lệnh giải thoát vùng nhớ cho các tham chiếu strong trên rồi mà tổng số tham chiếu vẫn chưa về 0 dẫn đếu việc là ARC vẫn chưa giải phóng vùng nhớ. Lý do là liên kết giữa PersonGadget vẫn còn và nó chưa bị phá huỷ cho dù instance của nó có là nil đi chăng nữa.

Đây chính là vòng tròn liên kết chặt chẽ, lý do chính dẫn đến việc bị thất thoát bộ nhớ. Để phá vỡ vòng tròn này, bạn cần phải sử dụng weakunowned.

Kiểu tham chiếu : weak

weak luôn được khai báo là optional vì giá trị của biến có thể đặt là nil. Để có thể chỉ ra tham chiếu là weak, bạn thêm từ khoá weak vào sau thuộc tính:

	weak var owner: Person?

ARC sẽ tự động đựat tham chiếu weak là nil khi instance bị giải phóng. Gía trị được gán là weak sẽ không thay đổi được giá trị của nó và như là một constant.

Chúng ta có thể phá vòng lặp liên kết chặt bằng cách sử dụng weak. Hãy thay đổi code như sau :

class Gadget {
    let model: String
    init(model: String) {
        self.model = model
        print("\(model) is being initialized")
    }
    weak var owner: Person?
    deinit {
        print("\(model) is being deinitialized")
    }
}

Và chạy Playgrounds :

Bạn sẽ thấy rằng chúng đã được giải phóng. Sau khi chuyển owner thành weak . Sự liên kết sẽ như sau :

Khi bạn đặt kelvin thành nil, nó thể được giải phóng bởi vì không còn tham chiếu chặt nào đến Person nữa.

Kiểu tham chiếu : unowned

unowned khá giống với weak khi nó có thể sử dụng để phá liên kết chặt. Nhưng điểm khác biệt là nó luôn phải có giá trị. ARC sẽ không đặt giá trị của nó là nil. Bởi vì unowned luôn phải có giá trị nên ta sửa như sau :

class Person {
    let name: String
        
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
        
    var gadget: Gadget?
    deinit {
        print("\(name) is being deinitialized")
    }
}
 
class Gadget {
    let model: String
    unowned var owner: Person
        
    init(model: String, owner: Person) {
        self.model = model
        self.owner = owner
        print("\(model) is being initialized")
    }
    
    deinit {
        print("\(model) is being deinitialized")
    }
}

Như bạn thấy ở trên, owner của Gadget được định nghĩa là non-optional là một unowned. Bởi vì nó mà khi khởi tạo phải thêm tham số là owner. Quan hệ giữa PersonGadget có chút khác biệt, Person có thể có gadget nhưng Gadget buộc phải có Person.

var kelvin: Person?
...
kelvin = Person(name: "Kelvin")
kelvin!.gadget = Gadget(model: "iPhone 8 Plus", owner: kelvin!)

Dưới đây là minh hoạ về quan hệ giữa các biến bây giờ.

Giờ ta sẽ phá vòng liên kết chặt bằng cách đặt kelvin về nil sau đó xem kết quả.

Đến đây là bài viết kết thúc, mong các bạn hiểu rõ tham chiếu strong, weakunowned . Cảm ơn các bạn đã đón đọc.

REF: https://www.appcoda.com/memory-management-swift/


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí