Objective-C
iOS
Swift

UIPasteboardがクラッシュすると思ったらしなかった件

最近はもっぱらSwiftばっかり書いているので、Objective-Cに戻りたくないなぁって思っていたんですけど、ちょっと触る機会があった際にUIPasteboardで妙なことが起こったので、そのことについて書きます。

そもそもUIPasteboardってなんぞや

アプリ内、もしくはアプリからアプリにユーザーデータ共有を支援するオブジェクトって公式ドキュメントを翻訳したGoogle先生が言ってました。

これを使うことで、アプリ内とか、アプリとアプリの間でStringとUIImageとかDataとかを簡単に受け渡したりできる仕組みって感じですね。
使い方に関しては散々語られているのでここでは省略。

で、今回の話

既存のObjective-Cで作成されたアプリでNSDataをUIPasteboardにセットして、別のアプリに渡す処理があったのですが、別のアプリの方でUIPasteboardにセットされたはずのNSDataが取れない問題が発生しました。

色々原因を探っていると、既存アプリ側でUIPasteboardにNSDataをセットする際に一定条件でnilがセットされる不具合が発生していることが発覚し、nilをセットしたらクラッシュするはずなのでは?って思ったのですが、既存アプリはクラッシュしていませんでした。

実際に検証してみた

UIPasteboardに値を置く際には以下のメソッドを使用します。

UIPasteboard.h
- (void)setValue:(id)value forPasteboardType:(NSString *)pasteboardType;

- (void)setData:(NSData *)data forPasteboardType:(NSString *)pasteboardType;

この二つのメソッドは、 setValue では、NSString, NSArray, NSDictionary, NSDate, NSNumber, UIImage, NSURLをセットするのに対し、 setData は、その名の通りNSDataをセットするという点で違います。

そしてこの二つのメソッドを使用してそれぞれnilが入ったオブジェクトをUIPasteboardにセットする処理を実行してみました。

UIPasteboardTest.m
- (void)setValueTest {
    UIPasteboard *pasteboard = [UIPasteboard pasteboardWithName:@"test" create:YES];

    NSString *string = nil;

    [pasteboard setValue:string forPasteboardType:@"public.text"];
}


- (void)setDataTest {
    UIPasteboard *pasteboard = [UIPasteboard pasteboardWithName:@"test" create:YES];

    NSData *data = nil;

    [pasteboard setData:data forPasteboardType:@"public.data"];
}

なんとsetValueの方はアプリがクラッシュし、setDataの方はクラッシュしませんでした。
その代わり、setDataの方では、以下のエラーメッセージがログに出ます。

Could not save pasteboard named test. Error: Error Domain=PBErrorDomain Code=0 "Cannot load representation of type public.data" UserInfo={NSLocalizedDescription=Cannot load representation of type public.data, NSUnderlyingError=0x60400044dec0 {Error Domain=PBErrorDomain Code=15 "No loader block available for type public.data." UserInfo={NSLocalizedDescription=No loader block available for type public.data.}}}

公式のsetValuesetDataのページでも特にこの違いに関する記述がなさそうなのでこの違いの理由はイマイチわかりませんでした。

今回の自分のように、気づかない間に、setDataにnilのNSDataを入れる可能性があるので、Objective-Cで使用する際は、setDataする際にnilチェックをしておいた方が、いざ出した時に入ってなくて焦るってことがないのでいいのではないかと思います。

まとめ

・UIPasteboardのsetValueの引数にnilのオブジェクトを渡したらクラッシュします。
・UIPasteboardのsetDataはnilのNSDataを引数に渡してもクラッシュしないけどエラーメッセージを吐きます。
・Objective-CでsetDataする場合はnilチェックして、セットする前に気づくようにしたい。
・setValueもsetDataも引数はnonnullなので、Swiftだったらnil入れれないし、入れたらビルド通らないから、Swift使うのが一番最強。