概要
Swift4環境でUIImageを写真アプリのアルバムに保存するクラスを作成しようとして少しハマったのでメモします。
UIImageを保存するにはUIImagePicker.hに以下のように定義されているUIImageWriteToSavedPhotosAlbum
メソッドを利用します。
UIKit側の仕様
// Adds a photo to the saved photos album. The optional completionSelector should have the form:
// - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo;
UIKIT_EXTERN void UIImageWriteToSavedPhotosAlbum(UIImage *image, __nullable id completionTarget, __nullable SEL completionSelector, void * __nullable contextInfo) __TVOS_PROHIBITED;
公式ドキュメントには以下のように記載されています。
func UIImageWriteToSavedPhotosAlbum(_ image: UIImage,
_ completionTarget: Any?,
_ completionSelector: Selector?,
_ contextInfo: UnsafeMutableRawPointer?)
引数に指定したimageの保存処理が完了した場合に引数のセレクタで指定したメソッドが呼ばれます。メソッドは次のシグネチャに従っている必要があります。
The method selector of the completionTarget object to call. This optional method should conform to the following signature:
- (void)image:(UIImage *)image
didFinishSavingWithError:(NSError *)error
contextInfo:(void *)contextInfo;
作成したクラス
以下のようなクラスを作成しました。
final class PhotoSaver {
private var completion: (_ error: Error?) -> Void = { _ in }
func save(image: UIImage, completion: @escaping (_ error: Error?) -> Void) {
self.completion = completion
UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
}
@objc
private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
completion(error)
}
}
PhotoSaverはUIImageWriteToSavedPhotosAlbumメソッドを扱いやすくするためのラッパークラスです。セレクタ指定でなく、コールバック用のcompletionプロパティを持たせることにより、完了時の処理をクロージャーで記載できます。
ハマったポイント
実際に上記のコードを実行してみると、SIGABRTでクラッシュしてしまいます。コンソール上には以下のログが出力されます。
class 'Sample.PhotoSaver' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector -[Sample.PhotoSaver methodSignatureForSelector:]
Unrecognized selector
と出ているのでどうやらセレクタのシグネチャが正しくなくセレクタが動的に実行できていないようです。**はじめは作成した以下メソッドのシグネチャがおかしいのかと思っていました。**Web上の記事を探すと、メソッド名が違ったり、Error?でなくError!にっていたり、様々なシグネチャが見つかります。
@objc
private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
completion(error)
}
解説
もう一度よくエラーログを見てみます。
Unrecognized selector -[Sample.PhotoSaver methodSignatureForSelector:]
作成したメソッドのシグネチャがおかしいのでなく、作成したクラスがmethodSignatureForSelectorを実行できないというエラーです。
methodSignatureForSelectorのドキュメントを調べると、このメソッドはNSObjectで実装されているメソッドのようです。
Apple - methodSignatureForSelector
つまり、methodSignatureForSelectorを実行できるようにするには、NSObjectを継承してあげれば良いということになります。
final class PhotoSaver: NSObject {
private var completion: (_ error: Error?) -> Void = { _ in }
func save(image: UIImage, completion: @escaping (_ error: Error?) -> Void) {
self.completion = completion
UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
}
@objc
private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
completion(error)
}
}
これで正常に完了時の処理が実行されるようになります。