Skip to main content

Hiểu rõ hơn về weak và unowned trong Swift

Mặc dù Automatic Reference Counting (ARC) đã giải quyết cho chúng ta khá nhiều vấn đề về việc quản lí bộ nhớ , nhưng đôi khi chúng ta vẫn cần phải quản lí các references khi chúng ta không làm việc với các value types ( kiểu tham trị )
Khi nói về quản lí bộ nhớ , thì chắc hẳn tất cả những ai đã từng code swift đều đã được nghe đến 2 từ khoá weak và unowned
Việc dùng weak và unowned trong swift sẽ rất dễ gây nhầm lẫn không chỉ riêng những bạn mới học swift , mà kể cả những bạn đã có kinh nghiệm đôi khi cũng hay nhầm lẫn về 2 cách dùng này .
Trong Swift, chúng ta cần sử dụng weak và unowned để cung cấp cho ARC thông tin cần thiết giữa các mối quan hệ trong code của chúng ta. Việc không sử dụng weak hoặc unowned về cơ bản là bạn nói với ARC rằng cần phải có một tham chiếu mạnh ( strong reference ) nhất định và bạn có thể ngăn chặn số lượng tham chiếu về không. Việc không sử dụng đúng các từ khóa này, chúng ta có thể gây rò rỉ bộ nhớ trong ứng dụng của chúng ta . Cái gọi là Strong Reference Cycles hoặc Retain Cycles cũng có thể xảy ra nếu weak và unowned không được sử dụng đúng cách .
Lưu ý : Reference counting chỉ áp dụng cho các instances của classes ( nôm na là kiểu tham chiếu ). Structures và enumerations là kiểu value types ( kiểu tham trị ) vì thế sẽ không phải lo vấn đề về Reference counting
Khi nào dùng Weak ?
Trước hết , tham chiếu weak luôn được khai báo là các biến kiểu optional vì chúng có thể tự động set là nil bởi ARC khi tham chiếu của nó bị ngắt . Hai class sau sẽ giải thích cho chúng ta khi nào dùng tham chiếu weak .
class Blog {
    let name: String
    let url: URL
    var owner: Blogger?

    init(name: String, url: URL) { self.name = name; self.url = url }

    deinit {
        print("Blog \(name) is being deinitialized")
    }
}

class Blogger {
    let name: String
    var blog: Blog?

    init(name: String) { self.name = name }

    deinit {
        print("Blogger \(name) is being deinitialized")
    }
}
Khi một class được giải phóng một message sẽ đươc in ra . Đoạn code ví dụ sau đây chúng ta đã định nghĩa hai instances là optional khi việc set chúng bằng nil . Mặc dù một số bạn có thể kỳ vọng sẽ chạy hai câu lệnh print , nhưng nó đã ko xảy ra :
var blog: Blog? = Blog(name: "SwiftLee", url: URL(string: "www.google.com")!)
var blogger: Blogger? = Blogger(name: "Antoine van der Lee")

blog!.owner = blogger
blogger!.blog = blog

blog = nil
blogger = nil

// Nothing is printed
Đây là kết quả của Retain Cycle. Chúng ta cần đưa ra một tham chiếu weak. Trong ví dụ này, chỉ cần một tham chiếu weak vì điều này sẽ phá vỡ vòng lặp. Ví dụ: chúng tôi có thể đặt tham chiếu yếu từ blog đến chủ sở hữu của nó :
class Blog {
    let name: String
    let url: URL
    weak var owner: Blogger?

    init(name: String, url: URL) { self.name = name; self.url = url }

    deinit {
        print("Blog \(name) is being deinitialized")
    }
}

class Blogger {
    let name: String
    var blog: Blog?

    init(name: String) { self.name = name }

    deinit {
        print("Blogger \(name) is being deinitialized")
    }
}

var blog: Blog? = Blog(name: "SwiftLee", url: URL(string: "www.avanderlee.com")!)
var blogger: Blogger? = Blogger(name: "Antoine van der Lee")

blog!.owner = blogger
blogger!.blog = blog

blog = nil
blogger = nil

// Blogger Antoine van der Lee is being deinitialized
// Blog SwiftLee is being deinitialized
Lưu ý: Khi mặc định khai báo bạn để là weak có thể bạn sẽ phải làm việc với optionals trong rất nhiều trường hợp đôi khi nó thực sự không cần thiết .
Khi nào sử dụng unowned self ?
Không giống như tham chiếu weak, một tham chiếu không phải dạng optional khi sử dụng unowned. Tuy nhiên, cả unowned và weak không tạo ra strong reference .
Trích dẫn từ tài liệu của Apple :
Sử dụng tham chiếu weak bất cứ khi nào nó hợp lệ để tham chiếu đó trở thành nil tại một thời điểm nào đó trong suốt vòng đời của nó. Ngược lại, sử dụng tham chiếu unowned khi bạn biết rằng tham chiếu đó chắc chắn sẽ không bao giờ là nil khi nó được set trong quá trình khởi tạo .
Nói chung, phải cẩn thận khi sử dụng unowned. Có thể là bạn đang truy cập vào một instance không còn ở đó nữa, nó sẽ gây ra crash. Lợi ích duy nhất của việc sử dụng unowned so với weak là bạn không phải đối phó với optionals. Túm lại, sử dụng weak luôn an toàn hơn trong mọi tình huống .
weak và unowned chỉ sử dụng với self trong closures phải ko ?
Không. chắc chắn không. Bạn có thể chỉ ra bất kỳ thuộc tính hoặc khai báo biến weak hay unowned miễn nó là kiểu tham chiếu. Do đó, điều này cũng có thể làm việc :
download(imageURL, completion: { [weak imageViewController] result in
    // ...
})
Và bạn thậm chí có thể tham chiếu nhiều instance vì về cơ bản nó là một mảng :
download(imageURL, completion: { [weak imageViewController, weak imageFinalizer] result in
    // ...
})
Kết luận :
Nhìn chung, nó là một chủ đề khá khó để hiểu. Tốt nhất là bạn nên bắt đầu với việc đọc tài liệu Swift để hiểu sâu hơn về chủ đề này. Ngoài ra, nếu bạn không chắc chắn, hãy sử dụng weak hơn unowned. Nó có thể cứu bạn khỏi những lỗi phức tạp .

Comments

Popular posts from this blog

Swift Tool Belt, Part 1: Adding a Border, Corner Radius, and Shadow to a UIView with Interface Builder

During my iOS work, I’ve assembled a set of code that I bring with me on every iOS project. I’m not talking about large frameworks or CocoaPods here. These are smaller Swift extensions or control overrides that are applicable to many projects. I think of them as my tool belt. In this post, I’ll show you an extension that will add a border, a corner radius, and a shadow to any UIView, UIButton, or UILabel and allow you to preview what it will look like in Interface Builder. Back in 2014, I wrote a blog post on Expanding User-Defined Runtime Attributes in Xcode where I added a border, corner radius, and shadow to a UIView using Interface Builder’s user-defined runtime attributes. This solution had no type checking—you had to type the property you wanted to modify by hand and often had to look up what it was called. You also had to run your project in order to see the effect of the runtime attribute. Starting with Xcode 6 , there is a new mech...

Alamofire vs URLSession

Alamofire vs URLSession: a comparison for networking in Swift Alamofire and URLSession both help you to make network requests in Swift. The URLSession API is part of the foundation framework, whereas Alamofire needs to be added as an external dependency. Many  developers  doubt  whether it’s needed to include an extra dependency on something basic like networking in Swift. In the end, it’s perfectly doable to implement a networking layer with the great URLSession API’s which are available nowadays. This blog post is here to compare both frameworks and to find out when to add Alamofire as an external dependency. Build better iOS apps faster Looking for a great mobile CI/CD solution that has tons of iOS-specific tools, smooth code signing, and even real device testing? Learn more about Bitrise’s iOS specific solutions! This shows the real power of Alamofire as the framework makes a lot of things easier. What is Alamofire? Where URLSession...

Frame vs Bounds in iOS

This article is a repost of an answer I wrote on Stack Overflow . Short description frame = a view’s location and size using the parent view’s coordinate system ( important for placing the view in the parent) bounds = a view’s location and size using its own coordinate system (important for placing the view’s content or subviews within itself) Details To help me remember frame , I think of a picture frame on a wall . The picture frame is like the border of a view. I can hang the picture anywhere I want on the wall. In the same way, I can put a view anywhere I want inside a parent view (also called a superview). The parent view is like the wall. The origin of the coordinate system in iOS is the top left. We can put our view at the origin of the superview by setting the view frame’s x-y coordinates to (0, 0), which is like hanging our picture in the very top left corner of the wall. To move it right, increase x, to move it down increase y. To help me remember bound...