mixi engineer blog

*** 引っ越しました。最新の情報はこちら → https://medium.com/mixi-developers *** ミクシィ・グループで、実際に開発に携わっているエンジニア達が執筆している公式ブログです。様々なサービスの開発や運用を行っていく際に得た技術情報から採用情報まで、有益な情報を幅広く取り扱っています。

AssetsLibraryについて私が知っている二、三の事柄

こんにちは。mixiのiPhone版公式クライアントアプリを開発している七尾です。

私たちが開発しているmixiのiPhone版公式クライアントアプリは「つぶやき」や、「日記」、「フォト」、「チェックイン」などさまざまなサービスに対応しており、今後もどんどん機能を追加していく予定です。

今月リリースされたVer9.0ではプッシュ通知にも対応し、感慨もひとしおなのですが、実は同じくVer9.0では、写真の同時複数選択にも対応しており、目立たない機能の割に実装が大変だったので(笑)、新機能のご紹介がてら、実装で利用したAssetsLibraryフレームワーク周りで苦労した話や注意点などを書いておこうと思います。

AssetsLibraryはiPhone/iPadに保存された動画や写真に自由にアクセスする為の仕組みです。従来の標準で用意されているイメージピッカーだと、一枚しか選択できないのですが、AssetsLibraryを利用すると、自前のイメージピッカーを作るなどして、写真を一度に複数枚選択できるようになりました。

ALAssetsLibraryはクラスのプロパティに

もともと開発着手当時はiOS4で実装していましたが、ひと区切りついたところからiOS5で動かしてみたところ、iPhoneに保存されている写真アルバムの一覧の「[アルバム名](写真枚数)」を表示する部分が全て「null(0)」と表示されてしまいました。公式リファレンスでAssetsLibraryの項を眺めてみると、下記のように書いてあります。
The lifetimes of objects you get back from a library instance are tied to the lifetime of the library instance.

- iOS Developer Library "ALAssetsLibrary Class Reference" より

ALAssetsLibraryインスタンスから取得したALAssetなどのオブジェクトの寿命は、元のALAssetsLibraryインスタンスの寿命に準じるという事で、元のALAssetsLibraryを解放してしまうと、ALAssetsGroupやALAssetも一緒に解放されてしまうという事のようです。 なのでALAssetが用済みになってから、ALAssetsLibraryが解放されるようにしなければいけません。というわけでいっそのこと、ALAssetsLibraryはクラスのプロパティとして保持するようにした方が管理が楽でした。ちなみになぜiOS4で普通に動いていたのかは謎のままです。。。 また、明示的にreleaseしなくとも、なんとなくNSAutoreleasePoolとか使ってしまったせいで、せっかく取得した情報が一気に消し飛んでしまう、という惨事がありましたので気をつけましょう:-)

ALAssetを引き回さない

前述の通り、ALAssetは消えやすいです。 なので、各クラス間でALAssetをやりとりしたい時は、アセットのURLでやりとりするようにして、受け取った側でアセットURLからALAssetを取り出すようにした方が確実だと思います。

NSString *assetURL = [[assetObject valueForProperty:ALAssetPropertyURLs] objectForKey:[[assetObject defaultRepresentation] UTI]]
で、URL文字列が取得できて、

[assetLibrary assetForURL:assetURL
    resultBlock:^(ALAsset *asset) {
        // 結果取得時に実行されるブロック
    }
    failureBlock:^(NSError *error) {
        // 結果取得時に実行されるブロック
    }];
でURLからアセットオブジェクトを取得できます。 ちなみにresultBlockは非同期実行なので、アセット取得後にビューに反映させるなどの処理が必要であれば、resultBlockの中から実行するか、通知やデリゲートしてあげたりしなければいけません。非同期実行を意識せずに実装してしまうと、アセットがまだ取得できていないタイミングでビューの更新が行われてしまい、意図しない挙動の原因になってしまうので気をつけましょう。

保存もAssetsLibraryで

従来だとカメラで撮影した写真の保存などに、UIImageWriteToSavedPhotosAlbum()を利用している場合が多いと思いますが、AssetsLibraryなら

-(void)writeImageToSavedPhotosAlbum:(CGImageRef)imageRef
  metadata:(NSDictionary*)metadata
  completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock
が利用できます。completionBlockには

(^ALAssetsLibraryWriteImageCompletionBlock)(NSURL *assetURL, NSError *error);
でURLが返ってくるので、保存完了と同時にアセットのURLを取得する事ができます。 カメラで取った写真をすぐにALAssetオブジェクトとして利用したい場合に便利です。

[番外編]ターゲットOSは4.2以降だと楽

ちょっと事情が複雑なのですが、結論から言うと、位置情報サービスが利用できるかどうかの判別が簡単だからです。

まず、AssetsLibraryを利用する為には位置情報サービスが有効になっている必要があります。
位置情報サービスがオフになっていると、エラーダイアログが出てしまい、AssetsLibraryを利用する事ができません。
なので、AssetsLibraryを利用する手前で位置情報サービスの利用可否をチェックしなければいけません。
ここで考慮しなければならない点が2つあります。

(1)位置情報サービスがオンになっているか?

iOS4.0以降だと、

if (YES==[CLLocation locationServicesEnabled]) {
    // 位置情報サービスがオンになっている
}
でチェックできるので問題ありません。

(2)アプリごとの設定で位置情報サービス取得が許可されているか?

位置情報サービスがオンになっていても、アプリごとの設定でオフにされている場合は、

if (kCLAuthorizationStatusAuthorized==[CLLocation authorizationStatus]) {
    //アブリごとの設定でもオンになっている
}
でチェックできるのですが、実はこれ、iOS4.2以降からの機能です。 iOS4.2より古いバージョンについては、実際に位置情報を取得してみないと判断できません。実際に位置情報の取得を実行した後のCLLocationDelegate(現在地を取得開始した後に成功、あるいは失敗した時に実行されるメソッド群)でやっと判別する事ができます。

-(void)locationManager:(CLLocationManager *)manager didFailError:(NSError *)error {
    if ([error code]==kCLErrorDenied) {
        // アプリごとの設定で許可されていない事が判明
    }
(以下略)
現在のVer9.1のmixi公式クライアントアプリではサポートするOSのバージョンはiOS4.0以降で、URLスキーム呼び出しなどにも対応しています。なのでアプリ起動時、復帰時、URLスキームからのカメラ呼び出しなどの全てにおいて、事前に位置情報が取得できるかのチェックをする為、

// アプリの起動時に呼ばれる(プロセスが無い状態からの起動)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;

// アプリの復帰時に呼ばれる(プロセスが残っている状態からの起動)
- (void)applicationWillEnterForeground:(UIApplication *)application;
で位置情報が取れるかどうかのチェックをしています。

写真複数枚選択の実装を終えて

というわけで結構ハマりどころが満載でして、なぜ複数枚選択できるアプリが少ないのかがわかりました。。。他に実装しているアプリも少ないので、iPhoneユーザの方は是非一度お試しください。