How to Use Instruments in Xcode - Part 4
Tiếp tục từ phần trước: https://viblo.asia/thevinh92/posts/zb7vD81KvjKd dựa theo hướng dẫn từ: http://www.raywenderlich.com/23037/how-to-use-instruments-in-xcode Ở phần trước, ta đang dang dở với Generation, và nhờ đó đã phát hiện ra được lỗi Umbounded growth memory là do app không bao giừo clear ...
Tiếp tục từ phần trước: https://viblo.asia/thevinh92/posts/zb7vD81KvjKd dựa theo hướng dẫn từ: http://www.raywenderlich.com/23037/how-to-use-instruments-in-xcode
Ở phần trước, ta đang dang dở với Generation, và nhờ đó đã phát hiện ra được lỗi Umbounded growth memory là do app không bao giừo clear cached đi. Để fix, bạn chỉ cần thêm vào ImageCache việc lắng nghe memory warning notification mà UIApplication sẽ bắn ra. Khi ImageCache nhận được notification đó, nó sẽ clear cache đi. Để làm cho ImageCache lắng nghe notification, thêm đoạn code sau vào việc initializer và de-initializer:
init() { NSNotificationCenter.defaultCenter().addObserverForName( UIApplicationDidReceiveMemoryWarningNotification, object: nil, queue: NSOperationQueue.mainQueue()) { notification in self.images.removeAll(keepCapacity: false) } } deinit { NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationDidReceiveMemoryWarningNotification, object: nil) }
Đoạn code trên register 1 observer cho UIApplicationDidReceiveMemoryWarningNotification để thực hiện khối lệnh trong closure: removeAll Images. Để test xem đoạn code trên đã fix được lỗi chưa, hãy lặp lãi những bước ở phần 3 mà ta đã thử, đừng quên giả lập 1 memory warning, và generation analysis sẽ trông như sau:
Bạn có thể thấy rằng việc sử dụng bộ nhớ đã giảm sau khi có memory warning. Vẫn có 1 số memory growth overall, nhưng không còn nhiều như lưc trước. Lý do vẫn còn growth ở đây thực sự là do system libraries, và dĩ nhiên bạn không thể can thiệp nhiều vào nó được. Nó có vẻ như system libraries không hề giải phóng bộ nhớ, có thể là do design có thể là do bug. Tất cả những gì bạn có thể làm là free memory nhiều nhất có thể. Cuối cùng còn 1 vấn đề về leak nữa mà ta đã nói đến ở phần trước:
Strong Reference Cycles
Cuối cùng bạn sẽ tìm thấy 1 strong reference cycle trong app Flickr Search. Như đã đề cập từ trước, 1 strong reference cycle xảy ra khi 2 objects giữ trong references đến nhau, không thể deallocated được và gây ra tốn bộ nhớ. Bạn có thể phát hiện ra điều này bằng cách sử dụng Allocations instrument bằng một cách khác:
*chú ý: Để theo được phần này của tutorial, bạn cần phải profile app ở trên device thật. Không may rằng tại thời điểm viết tutorial, có 1 bug xảy ra khi chạy Allocations trên simulator, hầu hết các class sử dụng trong project đều không hiển thị trên Instruments.
Close Instruments, quay trở lại Xcode, và chắc chắn là device của bạn đã được chọn làm build target. Chọn ProductProfile rồi chọn Allocations template:
Lần này bạn sẽ không sử dụng generation analysis. Thay vào đó, bạn sẽ nhìn vào số lượng các object có types khác nhau mà được "hang around in memory". Bạn sẽ thấy 1 số lượng lớn các objects trong detail panel - quá nhiều để xem xét. Để thu hẹp số lượng objects cần phải xem xét, nhập "instruments" như 1 bộ lọc vào trong textfield phía trên của Allocations Summary list. Nó sẽ show ra chỉ các objects mà liên quan đến từ "Instrument", bởi vì app của chúng ta tên là "InstrumentsTutorial", Allocations list bây h sẽ chỉ show ra các loại mà được định nghĩa là 1 phần của project.
2 cột đáng chú ý trong Instruments là #Persistent và #Transient. cột Persistent giữ 1 số của số lượng các đối tượng thuộc mỗi type mà đang tồn tại trong bộ nhớ. Cột Transient cho thấy số lượng các đối tượng mà vẫn đang tồn tại nhưng đang được deallocated. Persitent object đuwocj sử dụng trên bộ nhớ, còn transient objects thì đã được giải phóng.
Bạn có thể thấy rằng có 1 persistent instance của ViewCOntroller - chính là screen mà bạn đang nhìn vào. Đương nhiên có cả AppDelegate, và 1 instance của Flickr API client.
Quay trở lại app, Thực hiện 1 tìm kiếm và đi sâu vào kết quả. Chú ý rằng 1 loạt các đối tượng mới được hiển thị trong Instruments: FlickrPhoto được tạo ra khi parsing kết quả search, SearchResultsViewController, và ImageCache cũng đang ở đây. ViewController instance vẫn là persistent, bởi vì nó cần thiét cho navigation controller. ok thôi, bây giờ nhấn vào back button, SearchResultsViewController sẽ được pop off khỏi navigation stack, nên nó sẽ bị deallocated. Nhưgn nó vẫn show cho chúng ta thấy rằng: 1 #Persistent count of 1 trong Allocations summary. Tại sao nó vẫn ở đây? Thử thêm 2 tìm kiếm nữa và ta sẽ thấy bây giờ có 3 SearchResultsViewControllers. Trên thực tế, nếu những view controllers này vẫn còn lằng nhằng trong bộ nhớ tức là có cái gì đó giữ 1 strong reference đến chúng. Có vẻ như bạn đang có 1 strong reference cycle.
Đầu mối chính của bạn ở đây khôgn chỉ là việc lưu lại SearchResultsViewController, mà còn cả toàn bộ các SearchResultsCollectionViewCells. Có vẻ như việc reference cycle là ở giữa 2 classes. Ở thời điểm bài viết đăng trên raywwenderlich, output của Instruments trên Swift vẫn không đặc biệt hữu ích trong 1 số trường hợp. Instrument chỉ có thể đưa bạn 1 số gợi ý về trục trặc nằm ở đâu, và cho bạn thấy nơi objects allocated, việc tìm ra vấn đề là của bạn.
Hãy nghiên cứu kỹ lại code: Di chuột qua InstrumentsTutorial.SearchResultsCollectionViewCell trong Category column, và click vào mũi tê nhỏ ở bên phải. View tiếp theo sẽ show cho bạn thấy tất cả các alloctions của SearchResultsCollectionViewCells trong lúc app chạy như ở dưới:
Thay đổi inspector để hiện ra Extended Detail inspector, bằng cách click vào icon thứ 3 ở bên trên của panel. Inspector này sẽ show ra stack trace của allocation đang được chọn hiện tại. Với phần đầu của stack trace, phần màu đen là code cảu bạn. double-click voà dòng đầu tiên màu đen (mà bắt đầu "InstrumentsTutorial") để thấy ở đâu cell được allocated.
Cells được allocated ở phần đầu của collectionView(cellForRowAtIndexPath:). Nếu bạn kéo xuông vài dòng, bạn sẽ thấy:
cell.heartToggleHandler = { isStarred in self.collectionView.reloadItemsAtIndexPaths([ indexPath ]) }
Đây là closure mà xử lý việc tap vào heart buttons trong collection view cells. Đây là nơi strong reference cycle xảy ra. nhưng rất khó để chỉ ra trừ khi bạn đã từng nhìn thấy nó. Closure của cell refer tớ SearchResultsViewController sử dụng self, mà tạo ra 1 string reference. Closure captures self. Swift thực sự bắt buộc bạn phải sử dụng rõ ràng từ seft trong closures (Trong khi bạn thường bỏ qua khi refer đến methods hoặc properties của chính object đó). Điều này giúp bạn hiểu xa hơn về việc thực sự bạn đang nắm giữ nó. SearchResultsViewController cũng có 1 strong reference đến các cells, thông qua collection view.
Để phá vỡ 1 trong reference cycle,bạn phải định nghĩa 1 capture list như là 1 phần của closure's definition. 1 capture list được sử dụng để khai báo các instances (mà bị captures bời closures) theo kiểu weak hoặc unowned:
- Weak: Nên sử dụng khi captured reference có thể trở thành nil trong tương lai. Nếu như object được refer tới bị deallocated, reference sẽ trở thành nil. Như vậy, chúng là optional types.
- Unowned: nêu sử dụng khi closure và object mà nó refer sẽ luôn luôn có thời gian sống tương đương nhau, và sẽ bị deallocated cùng 1 lúc. 1 unowned reference có thể không bao h nil.
Để fix strong reference cycle, thay đổi code như sau:
cell.heartToggleHandler = { [weak self] isStarred in if let strongSelf = self { strongSelf.collectionView.reloadItemsAtIndexPaths([ indexPath ]) } }
Declare self là weak có nghĩa là SearchResultsViewController có thể bị deallocated cho dù các cells của collection view giữ reference đến nó, vì giờ đây chúng chỉ là weak reference. Và deallocated SearchResultsViewController sẽ deallocate collection view và từ đó, deallocate cả các cells.