Handoffの3つのデータ受け渡し方法
サンプルコード
https://github.com/sonsongithub/HandoffSample
データの受け渡し
ネイティブアプリケーション同士でのHandoffでは,データを受け渡すことができる. 3種類の受け渡し方法があるが,通信方法やデータの種類に応じて,適切な方法を選ぶ必要がある. BluetoothとWiFiを利用して通信するが,やはり大量のデータを安易にHandoffでやり取りするのはよくないようだ.
userInfo
NSUserActivity
オブジェクトのuserInfo
にデータを引き渡し,通信することができる.
NSUserActivity
は,周囲のiOS/OSXデバイスへのブロードキャストに用いられることを考えると,大きなデータを割り当てるべきではないことは明白である.
この方法は最も簡単なので使いがちだが,画像や音声などの大きなデータを受け渡すことは推奨されない.
URLや数値,文字列程度にとどめておくべきだろう.
streamを使う
Handoffには,それを受け入れた後に,ストリームによる送受信の通信経路を提供する機能が備わっている.
それを利用すれば容易にデータをやり取りすることが可能だ.
ストリームはOSが自動的に確保してくれる.
送信側がストリームを使うことを明示し,受信側からストリームを開くことで,通信を開始できる.
ストリームのためにNSInputStream
とNSOutputStream
のインスタンスがOSから提供される.
まずは,送信側のコードを見てみる.
@interface ViewController () <NSUserActivityDelegate> {
NSUserActivity *_activity;
NSData *_dataToSend = nil;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_activity
= [[NSUserActivity alloc] initWithActivityType:@"com.sonson.OSX.HandoffSample"];
_activity.title = @"Browsing";
_activity.supportsContinuationStreams = YES;
_activity.delegate = self;
_activity.userInfo = @{@"DataSize":@(_dataToSend.length)};
[_activity becomeCurrent];
}
- (void)userActivity:(NSUserActivity *)userActivity
didReceiveInputStream:(NSInputStream *)inputStream
outputStream:(NSOutputStream *)outputStream {
NSInteger dataSize = dataToSend.length;
NSInteger sendSize = 0;
NSInteger packetSize = 128;
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[outputStream open];
uint8_t *p = (uint8_t*)[_dataToSend bytes];
while (1) {
NSInteger bytesToSend
= (dataSize - sendSize) > packetSize ? packetSize : (dataSize - sendSize);
NSInteger result
= [outputStream write:p + sendSize maxLength:bytesToSend];
if (result < 0) {
NSLog(@"Error - %ld", result);
break;
}
sendSize += result;
if (sendSize >= dataSize) {
break;
}
}
[_activity invalidate];
[outputStream close];
outputStream = nil;
}
- (void)userActivityWasContinued:(NSUserActivity *)userActivity {
}
- (void)userActivityWillSave:(NSUserActivity *)userActivity {
}
@end
まず,NSUserActivity
クラスのsupportsContinuationStreams
をYES
にする.
そして,NSUserActivity
のdelegate
をセットし,デリゲートメソッド処理するように実装しよう.
ストリームを扱うために,userActivity:didReceiveInputStream:outputStream:
メソッドを実装する.
userActivity:didReceiveInputStream:outputStream:
メソッドは,受信側からストリームが開かれたタイミングで呼ばれる.
今回のサンプルは,送信側からNSOutputStream
を通じて,バイナリデータを送るものである.
@interface AppDelegate () {
}
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (BOOL)application:(NSApplication *)application
willContinueUserActivityWithType:(NSString *)activityType {
if ([activityType isEqualToString:@"com.sonson.OSX.HandoffSample"])
return YES;
return NO;
}
- (void)application:(NSApplication *)application
didFailToContinueUserActivityWithType:(NSString *)userActivityType
error:(NSError *)error {
NSLog(@"application:didFailToContinueUserActivityWithType:error:");
NSLog(@"%@", error);
}
- (BOOL)application:(NSApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void(^)(NSArray *restorableObjects))restorationHandler {
[userActivity getContinuationStreamsWithCompletionHandler:^(NSInputStream *inputStream, NSOutputStream *outputStream, NSError *error) {
if (error == nil) {
NSNumber *num = userActivity.userInfo[@"DataSize"];
NSInteger dataSize = num.integerValue;
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
NSInteger receivedBytes = 0;
NSInteger length = 256;
uint8_t buffer[length];
NSMutableData *readData = [NSMutableData data];
while (1) {
NSInteger bytesRead = [inputStream read:buffer maxLength:length];
if (bytesRead <= 0)
break;
[readData appendBytes:buffer length:bytesRead];
receivedBytes += bytesRead;
if (receivedBytes >= dataSize)
break;
}
[inputStream close];
}
else {
NSLog(@"%@", [error localizedDescription]);
}
}];
restorationHandler(@[]);
return YES;
}
@end
iCloudを使う
UIDocument
/NSDocument
は,自動的にNSUserActivity
を持つらしい.
これを利用して,Handoffでドキュメントデータの識別子(URL?)を送り,それに基づきiCloudを通じて,ドキュメントを共有する仕組みが提供されている.
筆者は,まだこの機能をテストしていない・・・・.