2016年1月7日木曜日

Asset Catalog を使用しているときは [UIImage imageNamed:] が遅くなることがある

タイトルのとおりですが、本日発見してひどくパフォーマンスに影響が出たため注意喚起を兼ねて共有いたします。

最近のiOSプロジェクトは全て画像をxcassetsすなわちAsset Catalog経由で管理することが多いかと思いますが、このAsset Catalogを使用している際に[UIImage imageNamed:]経由で画像を取得するとiOS 8以前とは異なり大変画像の取得が遅くなることがあるようです。

詳細はこちら。
https://forums.developer.apple.com/thread/17888

実際に私の環境では目に見えて遅くなりました。Instruments経由で計測したところ最大で10倍ほど差が出ているように見えました。iOS 9.1以降は修正されているとのことですが、それでも[UIImage imageNamed:inBundle:compatibleWithTraitCollection:]を使用したほうがパフォーマンスへの悪影響が少ないとの情報があります。実際Instruments上でもTrait Collectionを検索するのに無駄なパワーを使っているように見えたのでパフォーマンスがタイトな箇所では自分で非同期ロードするなりキャッシュするなりして補ってあげたほうが良い気がします。

こちらからは以上です。

2015年12月17日木曜日

Xcode 7.2 の LLDB で Swiftのデバッグをするコツ

現在のXcode 7.2でSwiftを使ったiOSアプリのデバッグをするときのコツみたいなものをまとめました。将来的にはより良くなる可能性はあります。というか良くなってほしいです(´・_・`)

■LLDBはbreakした地点によって挙動が変わる

まずハマりどころがこれですが、現在のLLDBはbreakした地点で実行されていたコードがSwiftのコードかC言語系のコードかによってモードが変わります。
// Objective-C mode
(lldb) po [someObject property]

// Swift mode
(lldb) e someObject.property
Objective-Cモードの時にSwiftっぽい呼び出しをしたり、その逆をしてもまともにLLDBは動作しません。なので現在自分がどちらのモードのLLDBにいるのかを判断するのがキモになります。

ハマりどころとして、例えば実行中におもむろに停止ボタンを押してみるとか、debug viewボタンを押してみると、全部のコードがSwiftで書かれているアプリにも関わらずいきなりLLDBがObjective-Cモードで起動します。これはbreakされるのがシステムのC言語で書かれた領域だからです。

ちなみに現在のモードがどちらかを判断する方法は無いような気がします。気合と慣れで判断してください。オープンソースになりましたし、そのうち改善されると思います\(^o^)/

■SwiftモードのLLDBでprint object (po) したい

SwiftモードのLLDBのpoはかなり微妙な実装になっているので、SwiftモードでLLDBを使う場合にはpoの代わりにexprすなわちeを使うことをオススメいたします。
(lldb) e someObject
参照: http://stackoverflow.com/questions/28016227/when-debugging-swift-code-can-i-get-a-typed-reference-to-an-object-given-just-i

なんかこちらの記事ですとe -O -d run --で実行するといいよって書いてますけど何が違うのかはよくわかってません。個人的には普通のeでだいたい問題なかったです。

■SwiftモードのLLDBでポインタアドレスからオブジェクトを起こしたい

Objective-Cであれば
po 0x123456789012beef
で終わりなんですが、SwiftモードのLLDBはそこまで面倒を見てくれません。そこでunsafeBitCast()関数が便利に使えます。
(lldb) e let $v = unsafeBitCast(0x123456789012beef, MyView.self)
(lldb) e $v
これではかどりますね。

レガシーな Objective-C プロジェクトを Swift なプロジェクトに変換する

ここで言うレガシーなObjective-Cプロジェクトの定義とは
  • iOS 7時代 (Xcode 5) より前に作成されたプロジェクトである
  • Swiftのコードを一行も含んでいない
  • IOS_DEPLOYMENT_TARGETが8.0よりも小さい (7.xをサポートしている)
とします。

こんな由緒正しいiOSのプロジェクトを未だにメンテしている人もなかなかいないのかと思いますが、もしいらっしゃいましたらそんな方のためにSwiftなプロジェクトに変換していく方法をメモしておきます。

■前提条件

まずIOS_DEPLOYMENT_TARGETを8.0以上にしましょう。IOS_DEPLOYMENT_TARGETを8.0以上にすることでdynamic frameworkおよびclang moduleが使えるようになるため、Objective-CとSwiftの間の垣根が非常に低くなります。

■ケース1: 根っこはObjective-Cのまま、Swiftのファイルを追加

普通にSwiftのファイルを追加したらXcodeが上手いことしてくれます。具体的には

clang moduleを有効にしてdyldを使えるようにする設定
CLANG_ENABLE_MODULES = YES;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";

Swift化されるターゲットごとにブリッジングヘッダを作成
SWIFT_OBJC_BRIDGING_HEADER = "myapp/myapp-Bridging-Header.h";

Releaseビルド以外に対して最適化レベル設定
SWIFT_OPTIMIZATION_LEVEL = "-Onone";

が自動的に行われるので後はせいぜいブリッジングヘッダに使ってるObjective-Cのヘッダを書き込む程度ですみます。

■ケース2: 根っこからSwiftにする

「根っこからSwift」とは要するにmain.m(C言語のmain関数)を持たないアプリにしたいということですが、これも実は思ったより簡単にできます。
  • AppDelegate.swiftファイルを作成する
  • 既存のObjective-Cで書かれたAppDelegateを継承て新しくswiftなAppDelegateを作成して、@UIApplicationMainアノテーションを付ける
  • main.mを消す
参考: http://stackoverflow.com/questions/31309249/how-to-convert-objective-c-appdelegate-to-swift

たったのこれだけでプロジェクトの根っこがSwiftな状態になります。簡単ですね。

■さらにSwift化をすすめる

とりあえずAppDelegate.swiftを作ることで根っこはSwiftになるのですが、せっかくだからAppDelegateをまるごとSwiftにしてしまいたいものです。ここでもし、
  • UIWindowをAppDelegate内部でカスタマイズしている
  • UIApplicationをmain.mでカスタマイズしている
などの場合はそのままだとSwiftで対応するのがちょっと面倒です。

まずUIApplicationについてはInfo.plistのNSPrincipalClassを変更することで任意のカスタムクラスに差し替える事が可能です。参考はこちら: http://stackoverflow.com/questions/31642956/how-to-detect-all-touches-in-swift-2

UIWindowについてはMain.storyboardを使わなければ勝手にUIWindowを差し込まれることはなくなるので問題ないのですが、それでは困ると言う場合はawakeFromNibとかを実装してその中で
@UIApplicationMain
class MyAppDelegate: NSObject, UIResponder, UIApplicationDelegate {
  var window: UIWindow?
  override func awakeFromNib() {
    super.awakeFromNib()
    guard let defaultWindow = self.window else {
      fatalError("Something is wrong")
    }
    let window = MySuperDuperWindow()
    window.rootViewController = defaultWindow.rootViewController
    self.window = window
  }
}
とかすれば動くと思います。多分。