How to Use Instruments in Xcode - Part 2
Bài đăng này đã không được cập nhật trong 3 năm
Dựa trên hướng dẫn từ: http://www.raywenderlich.com/23037/how-to-use-instruments-in-xcode
Tiếp theo từ phần 1: https://viblo.asia/thevinh92/posts/7ymwGXp3G4p1
Trong phần trước chúng ta đang bàn đến Profiling:
Nếu bạn đang chạy 1 app Objective-C, thì có sẵn 1 option của "Show Obj-C Only": nếu bạn lựa chọn nó, chỉ có method Objective-C là được hiển thị, than vì function C hay C++. Trong trương trình của bạn thì ko có nhưng nếu bạn code OpenGL app, hoặc cocos2d-x thì nó có thể sẽ có kha khá code C++.
Có thể 1 số giá trị hơi khác 1 chút nhưng thứ tự của các entry sẽ giống như bảng dưới đây khi bạn Profiling app của chúng ta:
Có vẻ như nhìn vào bảng này chúng ta có thể thấy ko đc ổn lắm. Phần lớn thời gian được dùng cho việc apply filter "tonal" cho ảnh thumbnails. Để tìm hiểu xem method đó làm gì mà tốn thời gian như thế, click vào row đó và nó đưa bạn đến view như sau:
Method applyTonalFilter() là method được add thêm vào UIImage như 1 extension, và hầu hết 100% thời gian sử dụng là được dùng cho việc tạo ra output CGImage sau khi áp dụng image filter. Thực sự rất khó để có thể speed up bằng việc chỉnh sửa method này, bởi vì việc tạo ra 1 image là 1 quá trình chuyên sâu và nó mất bao nhiêu thời gian thì ta cũng phải chịu. Thay vào đó, ta nên nhìn lại xem method applyTonalFilter() được gọi ở đâu. Click "Call Tree" trong breadcrumb trail trên top của code view để trở về screen trước:
Tiếp theo click vào mũi tên nhỏ ở bên trái của dòng applyTonalFilter() ở trên đầu của table. Nó sẽ mở "Call Tree" ra để show caller của applyTonalFilter. Bạn có thể cần phải unfold cả những mũi tên tiếp theo; khi profiling Swift, đôi khi còn bị duplicate rows trong Call Tree, prefixed with @obj. Bạn sẽ thấy ở dòng đầu tiên, mà bắt đầu bằng tên target app của bạn (InstrumentsTutorial):
Trong trường hợp này, hàng này đề cập đến kết quả của method cellForItemAtIndexPath thuộc collection view. click đúp vào row đó để thấy code liên quan đến nó trong project. Bây h thì bạn có thể thấy vấn đề ở đâu. Method mà apply bộ lọc tonal mất nhiều thời gian để chạy, và nó được gọi trực tiếp trong cellForItemAtIndexPath, mà khi đó nó block main thread (và vì vậy block toàn bộ UI) mỗi khi nó cần phải fillter image.
Offloading the Work
Để giải quyết vấn đề này, bạn cần 2 bước: 1 là giảm tải việc filter image bằng cách thực hiện nó ở thread background với dispatch_async, thứ 2 là cache mỗi 1 image sau khi nó được tạo ra. Có 1 class rất nhẹ và đơn giản (với tên rất dễ nhớ ImageCache) đã được included vào project, nó lưu image vào memory và lấy ra dựa trên key.
Bạn có thể mở Xcode bằng cách nhất vào nút ở bên góc phải như hình dưới:
Tiếp theo, bên trong method collectionView(_:cellForItemAtIndexPath:), thay thế đoạn gọi loadThumbnail() như sau:
flickrPhoto.loadThumbnail { image, error in
if cell.flickrPhoto == flickrPhoto {
if flickrPhoto.isFavourite {
cell.imageView.image = image
} else {
if let cachedImage = ImageCache.sharedCache.imageForKey("\(flickrPhoto.photoID)-filtered") {
cell.imageView.image = cachedImage
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
if let filteredImage = image?.applyTonalFilter() {
ImageCache.sharedCache.setImage(filteredImage, forKey: "\(flickrPhoto.photoID)-filtered")
dispatch_async(dispatch_get_main_queue(), {
cell.imageView.image = filteredImage
})
}
})
}
}
}
}
Đoạn đầu của code vẫn giống với lúc trước: nó liên quan đến việc tải các ảnh thumbnail của Flickr từ web. Nếu ảnh đã đc thích => hiển thị thumbnail; nếu ko => hiển thị sau khi được filter bởi tonal. Điều chúng ta thay đổi là như sau: đầu tiên, check xem ảnh này đã có trong image cache chưa, nếu có rồi => ngon, hiển thị ra image view. Nếu chưa, gọi tới việc apply tonal filter cho ảnh và đc xử lý trong backgroung queue bằng dispatch_async. Điều này giúp cho UI vẫn còn có thể responsive trong khi image được filter. Khi filter đã đc apply, image được lưu lại trong cache và image view được updated trong main queue. Ở đây đã xử lý việc filter image, tuy nhiên vẫn còn cần quan tâm đến thumbnails original của Flickr. Mở file FlickrSearcher.swift, tìm loadThumbnail(_, thay thế bằng:
func loadThumbnail(completion: ImageLoadCompletion) {
if let image = ImageCache.sharedCache.imageForKey(photoID) {
completion(image: image, error: nil)
} else {
loadImageFromURL(URL: flickrImageURL(size: "m")) { image, error in
if let image = image {
ImageCache.sharedCache.setImage(image, forKey: self.photoID)
}
completion(image: image, error: error)
}
}
}
Code phía trên cấu trúc khá giống với code xử lý filter. Nếu 1 image đã tồn tại trong cache thì sẽ ngay lập tức thực hiện block trong completion, nếu không image sẽ được loaded từ Flickr và lưu trong cache. Re-run app trong Instruments bằng cách chọn Product/Profile hoặc nhấn ⌘I. Bạn sẽ thấy thời gian cho UI là ko quá nhiều như trước. filter image bây h được xử lý bất đồng bộ và image được cached ở background vì thế nó chỉ bị filter 1 lần. Bạn cũng sẽ nhìn thấy 1 số lượng lớn các dispatch_worker_threads trong Call Tree - chúng chịu nhiệm vụ xử lý việc apply các image filter.
All rights reserved