try!swift workshop 'Testing and Performance Workshop'
try!swift 3日目のworkshopの一つである'Testing and Performance Workshop'に参加して来ました。
会場はこちらでした。
講師のSamuelさんがワークショップ用のリポジトリを作ってくれました。
パフォーマンスに問題のあるプロジェクトを直していこう!と言うものです。主に実施した2つを紹介していきます。
- ScrollingSmooth
- ImagesInTables
- ScrollingSmooth
※すでにmasterの最新は解決ずみのものが上がってますのでご注意ください
Initial commit with sample projects. · sgoodwin/PerformanceDemos@92fcf1e · GitHub
- 問題発見
これを実行すると...真っ白😮
これを調査します。
- 調査
XcodeのメニューからProduct>Profileを開き"Time Profiler"を開きます
すると....
CPU100%😇
お気付きの方も多いかも知れませんが、10,000,000回appendしており、また全てメインスレッドで行なっているので、描画処理が進まないと言う状態です。
override func viewDidLoad() {
super.viewDidLoad()
for _ in 0..<10_000_000 {
data.append(randomWord())
}
tableView.reloadData()
}
- 解決
そこでswiftでの非同期処理を実現するために"DispatchQueue"を使います。
override func viewDidLoad() {
super.viewDidLoad()
namesQueue.async { [weak self] in
guard let self = self else { return }
for _ in 0..<10_000_000 {
self.semaphore.wait()
self.data.append(self.randomWord())
self.semaphore.signal()
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
viewControllerの全体はこちらから
PerformanceDemos/ViewController.swift at master · sgoodwin/PerformanceDemos · GitHub
結果
Time Profilerで見るとメインスレッドが空き、サブスレッドが張り付いていることがわかります。
非同期処理を行うことで「時間のかかる処理は別の場所で実行する」と言うことが可能になります。
2.ImagesInTables
続いて名前から察してるかも知れませんがimageの読み込みです。
実行すると...カクカク😱
- 調査
CPU上ではさっきと違って100%と言うわけではありません。
ですが、コードを見るとセルの表示時にURLからデータを読み込んで、表示しています...
- URL先のdataを取る
- とったdataをUIImage型へ変換し、imageViewへ設定する
ということを行うので、メインスレッドが混雑してカクカクになってると思われます。
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.imageView?.image = UIImage(data: try! Data(contentsOf: URL(string: "https://via.placeholder.com/150.png?text=Demo+desu!")!))
cell.textLabel?.text = "row \(indexPath.row)"
return cell
}
- 解決
またもや" DispatchQueue"さんですよ。
今回はちょっと工夫してデフォルトイメージを設定してみました。
let defautImage:UIImage = UIImage(data: try! Data(contentsOf: URL(string: "https://via.placeholder.com/150.png?text=Demo+desu!")!))!
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let queue = DispatchQueue.global(qos: .default)
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.imageView?.image = defautImage
queue.async {
let data:UIImage = UIImage(data: try! Data(contentsOf: URL(string: "https://pbs.twimg.com/profile_images/828386643560181761/zVBAD623_400x400.png")!))!
DispatchQueue.main.async {
cell.imageView?.image = data
}
}
cell.textLabel?.text = "row \(indexPath.row)"
return cell
}
結果
サクサク😻
ほんとは画像キャッシュとかすると思いますが、時間がなかった(メンドくさい)ので解決です。
Time Profilerを見るとcellごとにスレッドできてますね。
(これはこれでいいのだろうか?)
画像読み込み周りはいつもライブラリに依存してしまっているので、仕組みがわかってないと困った時に解決策を考えられないなぁと感じました。
その他画面遷移で画面が重なりすぎないようにするお話と、UnitTestをやったのですが、そこまで行かなかったので、このブログはここでおしまいです😃
まとめ
- 重い処理はサブスレッドを駆使し、UIを更新するメインスレッドは空けておくような工夫をする
- 画像取得とキャッシュをよろしくやってくれるライブラリは神。先人に感謝。
- 自分のプロジェクトでTime Profiler開こう!どのくらい重い処理をしているのかしろう
以上です。みなさんも試してみてください!🤗