Skip to content

Instantly share code, notes, and snippets.

@shinyaohira
Last active May 25, 2016 11:34
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shinyaohira/6000519 to your computer and use it in GitHub Desktop.
Save shinyaohira/6000519 to your computer and use it in GitHub Desktop.
高度なアプリの技法+パフォーマンスチューニング

全体的に簡略化し、必要と思われる部分を抜粋しました。

適切に大きなスクリーンに対応するためには、デバイスの画面の大きさについて、何の仮定もしないことが大切です。画面やウインドウ、ビューの大きさを実行時に動的に取得し、インターフェイスを設定してください。また、ビューベースの制約を用いてユーザインターフェイスを 構築すれば、実行時にビュー階層の変化を容易に管理できます。

ユニバーサルアプリとは、iPhone、iPod touch、およびiPadデバイス向けに最適化された単一のアプリです。現在のデバイスに適合したバイナリを1つだけ提供することによって、最適なユーザ体験を提供することができますが、開発者には追加の作業が発生します。デバイスの画面サイズが違うため、iPad用のほとんどのウインドウ、ビュー、ビューコントローラのコードは、iPhoneとiPod touch用のコードとかなり異なる可能性があります。加えて、アプリが各デバイス上で正しく動作することを保証しなければなりません。

Xcodeにはユニバーサルアプリの設定支援機能が組み込まれています。新規プロジェクトを作成する際に、特定のデバイス向けにするか、ユニバーサルプロジェクトにするかを選択できます。対応デバイスはプロジェクト作成後でも、Summaryペインで変更可能です。特定のデバイス向けからユニバーサルプロジェクトに変更する場合は、追加するデバイスに関する情報を指定する必要があります。

Info.plistの設定を更新する

ユニバーサルアプリであっても、Info.plistファイルに使われているキーの多くはそのまま使えます。しかし、iPhone用とiPad用で値が異なるキーについては、キー名にデバイス修飾子を付加する必要があります。Info.plistファイルのキーを読むとき、システムは次の形式のキーを解釈します。

key_root - <platform> ~ <device>
  • key_rootの部分はキーの元の名前
  • <platform><device>の部分はどちらもオプショナル
  • iOS上でしか動作しないアプリの場合、platformは省略可能
  • キーを特定のデバイスに適用する場合、iphone, ipod, ipadを指定

iPhoneおよびiPod touchデバイスではアプリを縦向きで起動し、iPadでは右ホームの横向きで起動したい場合の例

<key>UIInterfaceOrientation</key>
<string>UIInterfaceOrientationPortrait</string>
<key>UIInterfaceOrientation~ipad</key>
<string>UIInterfaceOrientationLandscapeRight</string>

どのデバイスでも必ず有効な設定が存在するよう、デフォルトを用意してください。

ビューコントローラとビューを実装する

ビューについては、必要な主な変更は、より大きな画面をサポートするようビュー階層を設計し直すことです。既存のビューを単純に拡大しても機能はするかもしれませんが、通常は貧弱な結果しか生み出しません。新しいインターフェイスでは、利用可能なスペースを活用し、必要に応じて新しいインターフェイス要素をうまく利用するべきです。そうしたほうが、ユーザーにとってより自然なインターフェイスとなり、単に画面が大きくなっただけのiPhoneアプリではなくなります。

ビューコントローラについてのガイドライン

  • iPhone用とiPad用で、別々にビューコントローラのクラスを定義することを検討してください。多くの場合、単一のビューコントローラで両方に対応するよりも容易です。共通のコードが多いようであれば、その部分をベースクラスに実装し、デバイス固有の処理のみをサブクラスに記述するようにしてください。
  • 単一のビューコントローラをiPhoneでもiPadでも使う場合、両方の画面サイズに対応しなければなりません。(nibファイルを用いる場合は、デバイスに応じて読み込むファイルを選択することになります)。同じように、プラットフォームの違いもハンドルする必要があります。

ビューについてのガイドライン

  • iPhone用とiPad用で、別々にビューを用意することを検討してください。カスタムビューに関しては、デバイスごとにクラスを定義することになります。
  • 同じカスタムビューで兼用する場合は、特にdrawRect:メソッドとlayoutSubviewsメソッドがどちらのデバイスでも正常に動作することを確認してください。

リソースファイルを更新する

  • iPhoneで起動されるときに表示されるDefault.pngファイルに加え、iPad用の新しい起動画像を追加しなければなりません。
  • 画像を使用する場合、iPadをサポートするために、より大きな(より解像度の高い)画像の追加が必要になる可能性があります。
  • ストーリーボードやnibファイルを使用する場合、iPadデバイス用に新しいファイル一式を準備する必要があります。
  • アプリのアイコンは、iPadに適したサイズでなければなりません。

条件付きコードパスを作成するためにランタイムチェックを使う

デバイスタイプに応じてコードが異なるパスをたどらなければならない場合、UIDeviceのuserInterfaceIdiomプロパティを使用して決定します。最も簡単なのは、UI_USER_INTERFACE_IDIOMマクロを使用することです。

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  // iPad
}
else {
  // iPhoneまたはiPod touch
}

iOSの複数のバージョンをサーポートする

幅広いiOSのバージョンをサポートするアプリは、ランタイムチェックを使用し、旧バージョンのiOSでは動作しないAPIを使わないようにしてください。

  • 既存のクラスでメソッドが利用可能かどうかを判断するためには、instancesRespondToSelector:クラスメソッド、またはrespondsToSelector:インスタンスメソッドを使用します。
  • iOS SDK 4.2以降にリンクされているアプリでは、ウィークリンク機能を使用できます。この機能を利用すると、特定のClassのオブジェクトが存在するかをチェックし、そのクラスを使用できるかを判断することができます。
if ([UIPrintInteractionController class]) {
  // インスタンスを生成して使用する
}
else {
  // print interaction controllerは利用できない
}
  • Cベースの関数が利用可能かどうかを判断するためには、関数名とNULLのブール比較を行います。シンボルがNULLでなければ、その関数は使用できます。
if (UIGraphicsBeginPDFPage != NULL)
{
  UIGraphicsBeginPDFPage();
}

常にランドスケープモードで画面を表示するアプリは、このモードで起動するよう、システムに対して明示的に指定しなければなりません。通常、アプリはポートレートモードで起動します。そして、必要に応じてデバイスの向きに合うようにインターフェイスを回転します。ポートレートとランドスケープの両方をサポートするアプリの場合は、必ずポートレートモードでビューを設定してから、ビューコントローラを利用して任意の向きに回転する処理を行わなければなりません。ただし、アプリがポートレートをサポートせず、ランドスケープのみをサポートする場合は、最初からランドスケープモードで起動したように見せるために、以下のタスクを実行しなければなりません。

  • アプリのInfo.plistファイルにUIInterfaceOrientationキーを追加し、このキーの値をUIInterfaceOrientationLandscapeLeftまたはUIInterfaceOrientationLandscapeRightに設定してください。
  • ビューをランドスケープモードにレイアウトし、ビューのレイアウトオプションまたはオートサイジングオプションを正しく設定してください。
  • ビューコントローラのshouldAutorotateToInterfaceOrientation:メソッドをオーバーライドし、ランドスケープレフト、またはランドスケープライトに対してはYES、ポートレートに対してはNOを返してください。

Info.plistファイルでUIInterfaceOrientationキーを設定することは、UIApplicationのsetStatusBarOrientation:animated:メソッドをapplicationDidFinishLaunching:メソッドの実行初期に呼び出すことと同じです。

アプリの最初の起動時に、実行に必要なデータファイルや設定ファイルをセットアップすることができます。アプリ固有のデータファイルは、アプリケーションサンドボックスのLibrary/Application Support/<bundleID>/ディレクトリに作成してください。必要ならこのディレクトリをさらに分割し、整理して格納してもかまいません。また、必要であれば、アプリのiCloudコンテナディレクトリや、ローカルのDocumentsディレクトリなど、他のディレクトリに作成することも可能です。

アプリケーションバンドルにあるデータファイルを実行時に修正する場合は、ほかの場所にファイルをコピーし、そちらを書き換えるようにしてください。アプリケーションバンドル内のファイルを修正してはいけません。iOSアプリはコード署名されており、修正すると署名が無効になるため、以後、起動できなくなってしまうからです。このようなファイルは、Application Supportディレクトリ(あるいはサンドボックス内の書き込み可能なほかのディレクトリ)にコピーし、そちらを修正する以外に安全な方法はありません。

バックアップ処理は、iCloudによってワイヤレスで行われたり、あるいはユーザーがiTunesを使って同期操作をしたときに行われます。このときデバイス上のファイルは、ユーザのコンピュータまたはiCloudアカウントに転送されます。アプリケーションサンドボックス内のどこにファイルを置くかによって、バックアップや復元の対象になるかが決まります。アプリが大容量のファイルを作成して頻繁に変更する場合、バックアップ対象になる場所に当該ファイルを置くと、バックアップ処理にかなりの時間を要する可能性があります。ファイル管理コードを記述する際は、このことに留意する必要があります。

アプリバックアップのベストプラクティス

バックアップ操作や復元操作で準備しないといけないことは特にありません。デバイスがアクティブなiCloudアカウントを使えるなら、アプリデータは適当なタイミングでiCloud上にバックアップされます。さらに、デバイスをコンピュータに接続すれば、iTunesがデータファイルをインクリメンタルバックアップします。ただし、iCloudもiTunesも、次のディレクトリの内容はバックアップしません。

  • <Application_Home> / AppName .app
  • <Application_Home> /Library/Caches
  • <Application_Home> /tmp

同期に長期間かかるのを防ぐには、アプリのホームディレクトリ内のファイルの置き場所を注意深く選択しておく必要があります。大容量のファイルを扱うアプリケーションの場合、iTunesやiCloudへのバックアップには相応の時間がかかります。もちろんストレージ容量を圧迫することにもなるので、積極的に削除して空き容量を増やす、あるいはiCloudへのバックアップを無効にする、という手段をユーザは考えてしまうでしょう。このことを念頭に、アプリケーションデータの保存に関しては、次のガイドラインに従って実装してください。

重要なデータは<Application_Home>/Documentsディレクトリに置いてください。重要なデータとは、ユーザドキュメントなどユーザが作成した情報で、アプリで生成し直すことができないもののことです。

アプリがダウンロードまたは生成したファイル、必要に応じてアプリが再生成できるファイルはサポートファイルです。サポートファイルを格納する場所は、使っているiOSのバージョンによって異なります。

iOS 5.1以降の場合は、サポートファイルを<Application_Home>/Library/Application Supportディレクトリに保存してください。そして、setResourceValue:forKey:error:メソッドを使用してNSURLIsExcludedFromBackupKey属性を対応するNSURLオブジェクトに追加してください。この属性を追加すると、iTunesやiCloudにバックアップされなくなります。もし大量のサポートファイルがある場合は、それらをカスタムサブディレクトリに保存し、そのディレクトリに属性を適用してください。

- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
    assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
 
    NSError *error = nil;
    BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
                                  forKey: NSURLIsExcludedFromBackupKey error: &error];
    if(!success){
        NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
    }
    return success;
}

iOS 5.0.1の場合は、setxattr関数でバックアップを防ぐ属性を設定します。バックアップすべきでないファイルやフォルダを作成するたびに、このメソッドを実行する必要があります。

#import <sys/xattr.h>
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
    assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
 
    const char* filePath = [[URL path] fileSystemRepresentation];
 
    const char* attrName = "com.apple.MobileBackup";
    u_int8_t attrValue = 1;
 
    int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
    return result == 0;
}

iOS 5.0以前の場合は、サポートファイルを<Application_Home>/Library/Cachesディレクトリに保存してください。

キャッシュされたデータは、<Application_Home>/Library/Cachesディレクトリに置いてください。Cachesディレクトリに置くべきファイルとしては、データベースキャッシュファイルや、いつでもダウンロードし直せる雑誌、新聞、地図などがあります。アプリは、システムが空きディスク空間を増やすためにキャッシュしたデータを削除しても、適切に対処できるようにしておかなければなりません。

一時データは、<Application_Home>/tmpディレクトリに置いてください。一時データとは、長期間にわたって保存しておく必要がないデータのことです。使い終わったら削除して、デバイス上の空間を消費し続けないようにしなければなりません。

ユーザがアプリの更新をダウンロードすると、iTunesはその更新を新しいアプリディレクトリにインストールします。次に、古いインストールを削除する前に、古いインストールから新しいアプリディレクトリにユーザのデータファイルを移します。次のディレクトリ内のファイルは、更新プロセスの間に保存されることが保証されています。

  • <Application_Home> /Documents
  • <Application_Home> /Library

ほかのユーザディレクトリ内のファイルも同じように移動されますが、更新後にそれらのファイルがあるものとあてにすべきではありません。 メモリを効率的に使用する

空きメモリの容量が安全しきい値を下回ると、iOSは実行中のアプリに警告を通知します。アプリがこの警告を受け取った場合は、できるだけ多くのメモリを解放しなければなりません。そのためには、キャッシュ、画像オブジェクトその他、いつでも再生成できるオブジェクトの、強い参照を削除するのが最善です。

メモリ不足の警告を受け取る方法

  • アプリケーションデリゲートのapplicationDidReceiveMemoryWarning:メソッドを実装する。
  • UIViewControllerのサブクラスで、didReceiveMemoryWarningメソッドをオーバーライドする。
  • UIApplicationDidReceiveMemoryWarningNotification通知を受け取るように登録する。

データモデルに既知の消去可能なリソースが含まれている場合は、対応するマネージャオブジェクトがUIApplicationDidReceiveMemoryWarningNotification通知を受け取れるように登録して、消去可能なリソースの強い参照を直接削除させることもできます。この通知を直接処理することによって、アプリケーションデリゲートを介してすべてのメモリ警告呼び出しをルーティングする必要がなくなります。

iOSシミュレータ上でSimulate Memory Warningコマンドを使用すると、メモリ不足の状況下でのアプリケーションの動作をテストできます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment