Phát hiện memory leak với Xcode Instruments
Thuật ngữ memory leaks chỉ những vấn đề liên quan đến memory của ứng dụng, bao gồm việc memory không ngừng tăng trong quá trình sử dụng ứng dụng còn gọi là unbounded growth memory, hoặc có những vùng nhớ tồn tại nhưng không hề có một tham chiếu nào đến nó và không bao giờ bị hủy. Khi bạn là một lập ...
Thuật ngữ memory leaks chỉ những vấn đề liên quan đến memory của ứng dụng, bao gồm việc memory không ngừng tăng trong quá trình sử dụng ứng dụng còn gọi là unbounded growth memory, hoặc có những vùng nhớ tồn tại nhưng không hề có một tham chiếu nào đến nó và không bao giờ bị hủy. Khi bạn là một lập trình viên iOS thì bạn có thể gặp các vấn đề về memory leak. Mặc dù hiện tại Apple đã hổ trợ tính năng ARC (Automatic Reference Counting) trong trình biên dịch Objective C giúp bạn rất nhiều trong việc quản lý bộ nhớ, nhưng đôi lúc memory leak vẫn tồn tại. Bài viết này sẽ giúp các bạn phát hiện memory leak với xcode instruments. Có hai nguyên nhân chủ yếu gây ra memory leaks: Retain cycle: xảy ra khi hai hay nhiều đối tượng có strong reference với nhau làm cho đối tượng đó không bao giờ được release (Ví dụ: A -> B -> C -> A) Một đối tượng được phân bổ nhưng không bao giờ được giải phóng. Với Automatic Reference Counting thì điều này rất hiếm, mặc dù nó vẫn có thể xảy ra nếu bạn đang làm việc với low-level class không hỗ trợ ARC (Như là CoreFoundation classes) Memory leak có thể dẫn đến hiện tượng app crash khi iOS kill ứng dụng sử dụng quá nhiều bộ nhớ. Hoặc ứng dụng của bạn có thể có biểu hiện lạ như là kết quả của các đối tượng tiếp tục tồn tại mặc dù nó đã được release. Khi bạn muốn tìm ra các memory leak thì đầu tiên bạn nên chạy ứng dụng và quan sát biểu đồ sử dụng bộ nhớ trong Xcode. Nếu bộ nhớ sử dụng có xu hướng tăng lên khi sử dụng ứng dụng đó thì có khả năng một số bộ nhớ không được release một cách hợp lý.
Instruments tool cung cấp với Xcode có thể cung cấp cho bạn cái nhìn sâu hơn về vòng đời của các đối tượng được sử dụng trong suốt ứng dụng của bạn. Để bắt đầu, ta sẽ kết nối với device của bạn. (Tốt nhất là sử dụng một device thực cho điều này, vì Simulator không phải luôn luôn hoạt động giống như device thực, đặc biệt là khi nói đến các chi tiết ở low-level). Tiếp tục, Trong Xcode, ta chọn Product -> Profile, Xcode sẽ biên dich ứng dụng của bạn và start Instruments.
Dưới “Choose a profiling template for,”, bạn chọn Allocations instrument sau đó bấm "Choose". (Lưu ý: Leaks instrument cũng có thể hữu ích nhưng nó chủ yếu được sử dụng để phát hiện các memory mà không bao giờ được release chứ không phải là các memory leaks từ các retain cycle) Cấu hình các thông tin mà Instrument nên thu thập: Discard unrecorded data upon stop: Instrument có xu hướng thu thập nhiều thông tin hơn những gì nó có thể xử lý trong thời gian thực. Bỏ chọn tuỳ chọn này để nó kết thúc xử lý tất cả các hàng đợi raw data khi bạn nhấn Stop Discard events for freed memory: Chọn tuỳ chọn này giúp ta dễ dàng lọc ra các thông tin cần thiết mà Instrument thu thập được. Vì chúng ta muốn tìm các leak memory nên chúng ta không cần những thông tin về các đối tượng allocated và released chính xác. Record reference counts: Trong kết quả phân tích của bạn, một đối tượng chỉ được liệt kê nếu có reference count lớn hơn 1, vì vậy giá trị cụ thể không quan trọng. Ta sẽ bỏ chọn tuỳ chọn này vì nó thực sự có thể làm chậm ứng dụng của bạn. Recorded types: Nếu bạn có một số ý tưởng về đối tượng đang bị leak, bạn có thể lọc các loại recorded ở đây. Ở mức tối thiểu, bạn nên kiểm tra các tuỳ chọn để bỏ qua các loại với tiền tố “NS,” “CF,” và “malloc,” vì đây là các chức năng cụ thể của iOS hoặc các chức năng low-level. (Những loại đối tượng này có thể bị leak nhưng thông thường đó là vì các đối tượng khác có mức độ cao hơn bị leak) Giảm trừ số lượng thông tin mà Instrument thu thập và xử lý sẽ giúp ứng dụng của bạn đỡ chậm hơn khi đang chạy cùng Instrument. Bây giờ bấm nút record để khởi động ứng dụng và bắt đầu ghi dữ liệu. Bạn nên bắt đầu xem đồ thị bộ nhớ sử dụng trên Allocations track. Nhấp vào nút bánh răng để chuyển sang tab Display Settings và nhận thấy nút "Mark Generation". Mỗi lần bạn nhấn này, Instruments sẽ Gắn thẻ tất cả các đối tượng được phân bổ. Điều này sẽ cho phép bạn tìm các đối tượng mà không được deallocated đúng. Ví dụ, bạn có thể có một màn hình phân bổ một loạt các đối tượng (ví dụ UIViewControllers, views, và các custom class khác) và bạn mong đợi chúng sẽ được release khi màn hình dismiss. Đầu tiên ta click vào nút “Mark Generation” để đánh dấu đối tượng tồn tại lâu dài (Ví dụ singletons sẽ tồn tại trong suốt ứng dụng) và bất kỳ đối tượng liên quan với view hiện tại. Sau đó di chuyển đến màn hình mà bạn nghi ngờ là memory leak. Nhấp vào “Mark Generation” một lần nữa để gắn thẻ tất cả các đối tượng chỉ được phân bổ cho màn hình này. Kiểm tra cột “# Total”- Nó có thể là một số lớn, nhưng khi bạn rời khỏi màn hình thì nó sẽ trở về 0. Nếu không có nghĩa là có một số đối tượng vẫn chưa được release (nó có thể mất một lúc để Instruments làm việc. Nhấn "Mark Generation" một lần nữa để tiếp tục cập nhật) Tuỳ thuộc vào thiết kế của ứng dụng bạn, các đối tượng có thể tồn tại. Nhưng trong nhiều trường hợp, nếu “# Total” không trở về 0, nó có thể là memory leak. Nhấn vào mũi tên trong một vòng tròn bên cạnh bất kỳ generation nào để tập trung phân tích các đối tượng được tạo trong generation đó. Có một vài công cụ để thu hẹp phạm vi của memory leak. Các công cụ đó là: Allocations list Công cụ này sẽ liệt kê tất cả các đối tượng trong các generation vẫn còn “live” (có nghĩa là các đối tượng không được release). Điều quan trọng là phải hiểu rằng các đối tượng cha được giữ trong bộ nhớ thì tất cả các đối tượng con cũng vậy. Ví dụ, bạn có thể thấy nhiều trường hợp CoreAnimation hoặc class UIKit, nếu bạn có view tồn tại lâu. Thật không may, Instruments không thể nói cho bạn biết chính xác nơi xảy ra vấn đề. Nhưng nếu bạn nhìn vào đối tượng mà bạn nghi ngờ nên được release, kích đúp vào nó để chuyển đến các nguồn và nhìn thấy nơi mà nó được allocated. Điều này ít nhất cung cấp cho bạn một điểm khởi đầu tốt để bắt đầu xem xét code của bạn. Call trees Đây là công cụ tốt nhất để tìm ra gốc rễ của các leak lớn nhất nhờ sắp xếp cột “Bytes Used” theo thứ tự giảm dần. Bạn có thể đi sâu vào phương pháo tiếp theo bằng cách nhấn vào hình tam giác bên cạnh tên một phương thức (tip: nhấn Option + → để mở rộng toàn bộ cây) Giống như allocation list, call trees cũng có thể cung cấp bạn một khởi đầu tốt để tìm kiếm leak. Nhìn vào cột “Responsible Library” để xem nơi một method cụ thể được gọi. Vì memory leaks hầu như là vì code của bạn nên bạn nên tập trung vào các method thuộc dự án của bạn. Back to the Code Khi bạn nghĩ rằng đã tìm thấy đối tượng gây ra memory leak, cách nhanh nhất để xác nhận nghi ngờ của bạn là chỉ cần xem xét code sử dụng đối tượng đó. Chạy ứng dụng của bạn một lần nữa và lặp lại các thủ tục như trên, đánh dấu generation. Nếu bạn nhìn thấy số lượng đối tượng trở về 0 thì bạn đã tìm thấy memory leak, bạn chỉ cần tìm hiểu làm thế nào để sửa nó lại thôi. Troubleshooting Dưới đây là một số vấn đề tôi đối mặt khi làm việc cùng Instruments. Các vấn đề tiềm năng khác bao gồm: Nhấn nút record có thể không có hiệu lực (thời gian bắt đầu đếm lên, nhưng không có gì trong biểu đồ trong cửa sổ log). Ngắt kết nối và khởi động lại thiết bị của bạn. Instrument hiển thị địa chỉ hex thay vì tên method. Hãy chắc chắn rằng bạn khởi động Instruments từ dự án trong xcode chứ không run Instruments trực tiếp. Device của bạn sẽ xuất hiện trong Instruments, nhưng bạn không thể chọn nó vì nó bị vô hiệu hoá. Trong trường hợp này, đảm bảo rằng phiên bản Xcode bạn đang hỗ trợ các phiên bản iOS được cài đặt trên device của bạn (bạn có thể xác nhận điều này bằng cách kiểm tra Window -> Devices in Xcode). Hy vọng rằng, tất cả những điều này sẽ giúp bạn phát hiện và sửa chữa những memory leaks.