[iOS 10] PHFetchResult の enumerateObjects(_:) で Trailing Closure が使えない

2016.10.24

Photos フレームワーク

iOS デバイスに保存されている写真などのデータを取得するには、iOS 8 以降であれば Photos フレームワークを使う方法が一般的です。

[iOS 8] PhotoKit 2 – Photos Framework – モデルオブジェクトの取得

iOS 10 (というか Xcode 8) で実装した場合、ちょっとしたバグに遭遇したので、備忘録としてまとめました。

enumerateObjects(_:) の挙動

実装としては、ユーザーからの許可を得たあと、PHAsset クラスの fetchAssets(with:options:) メソッドを呼び出すことで、写真などのデータを PHFetchResult インスタンスとして取得することができます。

let result = PHAsset.fetchAssets(with: .image, options: nil)

この PHFetchResult では enumerateObjects(_:) というメソッドが提供されており、このメソッドを使うことで写真データを1件ずつ取得することができます。

var items = [PHAsset]()
let result = PHAsset.fetchAssets(with: .image, options: nil)
// 写真を1件ずつ取り出す
result.enumerateObjects({ asset, _, _ in
    items.append(asset)
})
print("写真は \(items.count) 枚")

この enumerateObjects(_:) は関数オブジェクトのみを引数とするため、Trailing Closure を使って、() を省略したいところです。下記のコードのような感じですね。

var items = [PHAsset]()
let result = PHAsset.fetchAssets(with: .image, options: nil)
// Trailing Closure を使った場合
result.enumerateObjects { asset, _, _ in
    items.append(asset)
}
print("写真は \(items.count) 枚")

しかしながら、このコードを書くと Ambiguous use of 'enumerateObjects' というコンパイルエラーになってしまいます。

ph-fetch-result-enumrate-objects

Stack Overflow を覗いてみると、同じようなエラーに遭遇した人を多く見かけました。

PHFetchResult で提供されている以下のシグネチャにおいて block だけを引数として渡した場合、どれを呼び出しているか曖昧になってしまっていることが要因となっていそうです。

func enumerateObjects(at s: IndexSet, 
              options opts: NSEnumerationOptions = [], 
                using block: @escaping (ObjectType, Int, UnsafeMutablePointer<ObjCBool>) -> Void)

func enumerateObjects(_ block: (ObjectType, Int, UnsafeMutablePointer<ObjCBool>) -> Void)

func enumerateObjects(options opts: NSEnumerationOptions = [], 
                using block: @escaping (ObjectType, Int, UnsafeMutablePointer<ObjCBool>) -> Void)

原因は何であれ、現状は Trailing Closure を使わないで記述するしかないようです。

objects(at:) を使った方法

PHFetchResult では、objects(at:) メソッドを使った取得方法も用意されています。引数に IndexSet インスタンス (インデックスのコレクション) を渡すことで、それに該当する PHAsset インスタンスが配列で取得できるというものです。

let result = PHAsset.fetchAssets(with: .image, options: nil)
let indexSet = IndexSet(integersIn: 0...result.count - 1)
let items = result.objects(at: indexSet)
print("写真は \(items.count) 枚")

個人的には、こちらの方法の方が気に入っています。

まとめ

今回遭遇したバグは、もしかすると Xcode の次期バージョンでは修正されているかも知れません。あくまで一時的な話として頭の片隅にでも入れておくようにしましょう。

参考