Bài viết được sự cho phép của tác giả Lê Xuân Quỳnh
Nếu bạn biết về ARC là gì và tại sao lại rò rỉ bộ nhớ trong ứng dụng IOS thì bạn có thể chuyển sang phần 2. Vì đây là 1 bài viết rất dài, đề cập đến các nội dung sau:
- Thế nào là rò rỉ bộ nhớ trong IOS(Memory Leaks)
- Tại sao lại Memory Leaks
- Cách mà ARC không thể giải phóng bộ nhớ
- Memory Leaks trong hàm Closure
- Giải pháp khả thi
- Những tình huống đặc biệt(Singleton và static Classes Memory leaks)
- Khác nhau giữa weak và unowned
- Xác định leaks sử dụng Memory Graph Debugger
- Một số quy tắc Thumbs
Swift sử dụng Automatic Reference Counting(ARC) để theo dõi và quản lý bộ nhớ ứng dụng. Trong đa số trường hợp, điều này nghĩa là sự quản lý chỉ làm việc trong Swift framework 1 cách tự động, và bạn không cần phải hiểu về quản lý bộ nhớ như nào. ARC tự động giải phóng bộ nhớ bởi việc khi 1 instances của class sẽ giải phóng khi không cần thiết nữa.
Phần 1: Memory leaks trong IOS
Một memory leak xảy ra khi bộ nhớ giữ chiếm giữ và không thể giải phóng bằng ARC bởi vì nó không phân biệt được thực sự có được sử dụng hay không. Và vấn đề phổ biến nhất là chúng ta tạo ra memory leaks khi tạo ra 1 vòng retained cycles – nắm giữ lẫn nhau xảy ra! Chúng ta sẽ trình bày kỹ hơn ở dưới.
Tìm việc làm ios hấp dẫn làm online tại nhà
Khi nào thì ARC không thể giải phóng bộ nhớ?
Mỗi khi bạn tạo ra 1 đối tượng(object) từ class, ARC sẽ lưu thông tin về vùng bộ nhớ mà đối tượng này sử dụng. Khi 1 object cần được giải phóng, thì làm thế nào ARC biết được điều đó. Để hiểu về vấn đề này chúng ta sẽ tìm hiểu cách ARC làm việc.
Mọi biến khởi tạo sẽ mặc định là kiểu strong. (strong là kiểu ràng buộc mạnh, weak yếu). Khi bạn tạo ra instance của nó với kiểu strong, thì ARC sẽ tăng biến đếm lên 1. Hiểu nôm na là ARC tạo ra 1 biến count = 0, khi obj tạo ra thì tăng lên 1, khi giải phóng thì trừ đi 1. Bao giờ biến count này = 0 nghĩa là nó không cần nữa, và ARC sẽ tự động giải phóng biến này ra. Cùng xem ví dụ sau:
var referenceOne: Person? = Person()
chúng ta tạo ra 1 referenceOne của lớp Person. ARC sẽ tự động cấp phát bộ nhớ cho nó kiểu strong và tăng count của biến này thành 1.
var reference2 = referenceOne
Sau khi thực hiện câu lệnh trên, chúng ta tạo 1 liên kết mạnh tới object Person bằng việc gán địa chỉ sang reference2. Như bạn thấy ở hình sau, biến count = 2 do 2 thằng cùng nắm giữ địa chỉ này.
referenceOne = nil
Chúng ta loại bỏ tham chiếu strong tới biến referenceOne bằng cách gán nil cho nó. Như hình sau biến count lại quay về là 1.
reference2 = nil
Tương tự, chúng ta bỏ tham chiếu strong tới biến reference2 và hiện tại biến count = 0.
Như vậy object đã tạo không còn ai tham chiếu, ARC sẽ xóa ra khỏi bộ nhớ. Đấy là cách mà ARC làm việc.
Thế tại sao bộ nhớ lại rò rỉ?
Như chúng ta hiểu ARC sẽ tự động xóa biến khỏi bộ nhớ khi count = 0, nhưng vì lý do nào đó mà count không bao giờ = 0 được, nên không giải phóng được bộ nhớ. Hãy đọc tiếp nhé!
Khi nào ARC cố gắng kiểm tra count của 1 biến RC(reference count)
Khi bạn làm việc với cocoa hay cocoa touch thì đơn giản là nó kiểm tra khi một hàm thoát khỏi vòng lặp trên thread. Luật kiểm tra như sau:
- Nếu không ai tham chiếu tới 1 object thì ARC giải phóng
- Nếu 1 object không có tham chiếu mạnh tới nó.
Sau đây là ví dụ cụ thể:
var user: User? = User() //1 tham chiếu tới user
var todo: Todo? = Todo() //1 tham chiếu tới todo
Như hình dưới, ARC tạo ra 1 tham chiếu strong tới user và todo.
user?.todo = todo //2 tham chiếu mạnh tới todo
todo?.associatedUser = user// 2 tham chiếu mạnh tới user
2 câu lệnh trên hoạt động như sau:
- Tăng RC của todo lên 2, todo có 2 chủ sở hữu, 1 lần tạo ra và 1 lần gán.
- Tăng RC của user lên 2, tương tự như trên.
user = nil //giảm RC của user còn 1
todo = nil //giảm RC của todo còn 1
Lý tưởng nhất là khi đặt user về nil thì đáng ra RC phải là 0. Tuy nhiên lúc này trong mỗi object lại tham chiếu mạnh tới nhau nên không thể giải phóng được. Và do vậy chúng ta tạo ra strong reference cycle – vòng tròn tham chiếu mạnh lẫn nhau không thể giải phóng được.
Giải pháp
Có 2 giải pháp, dùng tham chiếu yếu weak hoặc tham chiếu unowned.
Sửa lại code như sau:
var user: User? = User() //RC = 1 tới user
var todo: Todo? = Todo() //RC = 1 tới todo
Câu lệnh trên sẽ tăng RC cho mỗi biến lên 1.
user?.todo = todo //RC = 2 tới todo
todo?.associatedUser = user //RC = 1 tới user
RC = 2 tham chiếu mạnh tới todo, còn user có RC = 1 do associatedUser là tham chiếu yếu.
user = nil
Câu lệnh làm RC của user về 0.
ARC kiểm tra và xóa user ra khỏi bộ nhớ. Vậy todo còn 1 tham chiếu mạnh tới nó.
todo = nil
Lúc này todo có RC = 0 và bị xóa khỏi bộ nhớ.
Quy tắc: Khi 2 đối tượng tham chiếu lẫn nhau thì biến tham chiếu trở về weak hoặc unowned.
Memory Leaks trong Closure
Memory Leak in Closure = self refers to → object refers to → self
Closure là một hàm được tạo ra từ bên trong một hàm khác (hàm cha), nó có thể sử dụng các biến toàn cục, biến cục bộ của hàm cha và biến cục bộ của chính nó. Và khi sử dụng thì nó capture(chụp lấy) các biến hay hằng và ném vào trong scope của nó.
Thế nào là capturing?
Cách hoạt động:
- Chúng ta tạo 2 biến a,b với 2 giá trị khác nhau 20 và 30.
- Chúng ta tạo hà someClosure và captures a, b bởi tham chiếu mạnh.
- Khi phương thức someMethodThatTakeClosure gọi closure và nó sẽ trả về 2 giá trị tổng của a, b captures từ hàm viewDidLoad. Giá trị là 50
Bài viết gốc được đăng tải tại codetoanbug.com
Có thể bạn quan tâm:
- 8 highlight từ sự kiện Apple WWDC 2017 mà bạn cần phải biết
- Làm thế nào để trở thành một Frontend Developer
- Bí kíp toàn thư về React mà bạn cần phải biết (phần 1)
Xem thêm tuyển it lương cao hấp dẫn tại TopDev