Outlets nên là Weak hay Strong?

IBOutlet là 1 đối tượng mà chúng ta luôn gặp trong công việc hằng ngày, mỗi khi chúng ta làm việc với giao diện. Mặc định, khi bạn kéo 1 Outlet từ Interface Builder vào trong file source code thì Outlet này sẽ được khai báo bằng 1 biến với tham chiếu Weak. Nhưng có khi nào bạn tự hỏi rằng nếu nó được khai báo Strong thì nó ảnh hưởng ra sao đến ứng dụng của bạn? Trong bài viết này, chúng ta sẽ cùng nhau đi tìm ra câu trả lời.

Strong vs Weak?

Điều gì sẽ thay đổi nếu bạn thay đổi khai báo của outlet từ weak sang strong? Không gì cả?. Bạn có thể thử khai báo 1 Outlet với strong và chạy thử ứng dụng. Ứng dụng của bạn sẽ không bị crash hay ảnh hưởng gì.

@IBOutlet var label: UILabel!

Dường như chẳng có khác biệt nào đối với sự thay đổi này phải không, vậy tại sao nó lại quan trọng? Tại sao một số lập trình viên khai báo outlets là tham chiếu Strong, trong khi một số khác lại khai báo là Weak? Vậy chúng ta sẽ cùng tìm hiểu từng loại xem nó có gì khác nhau không nhé.

Strong Outlets

Để có thể hiểu tại sao outlets có thể khai báo là strong hay weak, bạn cần hiểu về việc 1 đối tượng nắm giữ 1 tham chiếu đến các đối tượng khác. Đúng vậy, đó chính là đếm số lượng tham chiếu.

Về cơ bản, mọi view controller giữ 1 tham chiếu đến những view mà chúng quản lý. Đó có thể là tham chiếu strong hoặc weak. Ví dụ: Tham chiếu giữa view controller và view gốc của nó là 1 tham chiếu strong chứ không thể là weak. Tại sao như vậy? Bởi vì nó cần được giữ tham chiếu strong bởi view controller để có thể tồn tại (Theo cơ chế của ARC thì đối tượng nào không được giữ bởi ít nhất 1 tham chiếu strong thì sẽ bị giải phóng khỏi bộ nhớ).

Bạn có thể kiểm chứng bằng cách mở file ViewController.swift, giữ Command và click vào UIViewController. Bạn sẽ được đưa đến class khai báo của UIViewController, và bạn có thể thấy thuộc tính view được khai báo strong.

@available(iOS 2.0, *)
open class UIViewController : UIResponder, NSCoding, UIAppearanceContainer, UITraitEnvironment, UIContentContainer, UIFocusEnvironment {

    ...
    
    open var view: UIView!

    ...

}

Điều gì sẽ xảy ra nếu 1 view mới được thêm vào view gốc của view controller với tham chiếu strong? Ví dụ, nếu chúng ta thêm 1 label cho view gốc của view controller. Một view luôn luôn giữ 1 tham chiếu strong đến những subviews mà nó quản lý. Điều này có nghĩa là label sẽ vẫn tồn tại và hiển thị thậm chí khi chúng ta không khai báo 1 outlet cho label đó trong ViewController. Superview của label sẽ giữ cho label tồn tại. Khi label được khai báo vào trong view controller, thì lúc này nó sẽ có 2 tham chiếu strong trỏ đến nó.

Mối liên hệ trên cho thấy, khi view controller được giải phóng khỏi bộ nhớ, dẫn đến view gốc sẽ có số tham chiếu strong = 0 và cũng được giải phóng theo. Sau đó, label sẽ mất 2 tham chiếu mạnh từ view controller và superview nên lúc này cũng sẽ có số tham chiếu đến = 0 và cũng được giải phóng.

Tuy nhiên, trong trường hợp view gốc được giải phóng trong khi view controller vẫn còn tồn tại thì label vẫn còn tồn tại do nó vẫn được giữ tham chiếu strong từ view controller.

Weak Outlets

Điều gì sẽ xảy ra nếu outlet được khai báo là weak? Với cách suy nghĩ như trên, chúng ta cũng có thể đoán ra điều gì sẽ xảy ra. View gốc vẫn giữ 1 tham chiếu strong đến label. Nhưng view controller sẽ chỉ giữ 1 tham chiếu weak đến label đó.

Bề ngoài thì trường hợp này cũng không khác trường hợp trên, vì label vẫn tồn tại bên trong view gốc. View gốc hay superview của label giữ cho label tồn tại. Khi view controller giải phóng, view gốc của nó cũng giải phóng theo. Cuối cùng thì label cũng bị giải phóng khi super view của nó đã được giải phóng trước đó.

Như vậy thì chẳng có khác biệt gì chăng? Không phải vậy. Điều gì sẽ xảy ra nếu view controller cố gắng truy cập đến label sau khi view gốc của view controller được giải phóng? Vì oulet của label được khai báo weak, nên label cũng sẽ giải phóng theo, lúc này biến lưu trữ label trong view controller sẽ tự động được set nil. Và vì biến này là 1 implicitly unwrapped optional, nên khi nó bị set nil nó sẽ dẫn đến crash ứng dụng khi view controller cố gắng truy cập đến nó.

Vậy khi nào thì sử dụng Weak hay Strong?

Dựa vào 2 trường hợp bên trên, chúng ta có thể thấy dường như cách tiếp cận bằng Strong tỏ ra an toàn hơn, vậy tại sao chúng ta lại cần sử dụng weak?

Tuy dùng strong khá là an toàn, nhưng nó lại gây ra tốn bộ nhớ cho ứng dụng do những subviews không tự động được giải phóng khi super view đã được giải phóng. Thông thường, trong thực tế sử dụng, các subviews thường có vòng đời đi kèm với super view của nó hoặc ngắn hơn, nên thường khi super view được giải phóng tức là chúng ta cũng không còn cần đến subviews bên trong của nó, và chúng cũng nên được giải phóng luôn khỏi bộ nhớ (Do đó thông thường khai báo mặc định khi kéo Outlet từ IB sang source code là Weak).

Tất nhiên, cũng có một số trường hợp chúng ta cần khai báo tham chiếu strong cho Outlet. Đó là khi mong muốn của chúng ta là luôn giữ cho Outlet đó còn tồn tại khi nó bị xoá (remove khỏi hệ thống view) để có thể truy cập đến nó hoặc có thể addSubview trở lại trong tương lai.

Tóm lại

Việc sử dụng Weak hay Strong còn tuỳ thuộc vào bối cảnh cụ thể và dụng ý của người sử dụng. Tuy nhiên cũng không nên sử dụng tham chiếu Strong nếu không thật sự cần thiết và để tránh việc gây lãng phí bộ nhớ cho ứng dụng của bạn.

Bài viết này tham khảo từ: https://cocoacasts.com/should-outlets-be-weak-or-strong/ . Tuy nhiên quan điểm của tác giả trong bài viết này có khác hơn 1 chút so với bài viết gốc bằng Tiếng Anh. Các bạn có thể tham khảo cả 2 bài viết để rút ra kinh nghiệm sử dụng cho riêng mình.