Multipeer connectivity是一个使附近设备通过Wi-Fi网络、P2P Wi-Fi以及蓝牙个人局域网进行通信的框架。互相链接的节点可以安全地传递信息、流或是其他文件资源,而不用通过网络服务。
概述从上图中可以看出Multipeer Connectivity的功能与采用AirDrop传输文件非常类似,也可以将其看做是Apple对AirDrop不能直接开发的补偿,关于Multipeer Connectivity与AirDrop之间的对比,可参考《MultipeerConnectivity.framework梳理》
因为iOS系统中会员不能直接对文件进行操作,所以这个框架很少会在app中使用到。这就导致了网上很少有关于介绍这个框架的博文,至于可供参考的demo那就更加少之又少了。但这并不意味着这个技术不实用,像QQ的面对面快传(免流量)功能就是采用这个框架实现的。所以我采用这个框架实现了一个文件传输的demo,这里分享出来,供大家一起学习。
实现功能demo最终实现的效果图如下:
实现功能如下:
1.可选择相册中的图片、视频进行传送
2.可将想传送的文件移动到工程中LocaFile目录下,然后选择本地文件就可传送
3.可扫描附近节点(只做了一个节点连接的情况 )
4.监控传输进度
连接要想让两个设备间能进行通信,必先让他们知道对象,这个过程就称之为连接。在Multipeer Connectivity框架中则是使用广播(Advertisting)和发现(Disconvering)模式来进行连接:假设有两台设备A、B,B作为广播去发送自身服务,A作为发现的客户端。一旦A发现了B就试图建立连接,经过B同意二者建立连接就可以相互发送数据。关于连接过程的更详尽介绍,可参考《 iOS--MultipeerConnectivity蓝牙通讯》。初始化代码如下
发送端代码:
接收端代码:
//创建会话 MCPeerID *peerID = [[MCPeerID alloc] initWithDisplayName:[UIDevice currentDevice].name]; self.session = [[MCSession alloc] initWithPeer:peerID securityIdentity:nil encryptionPreference:MCEncryptionRequired]; self.session.delegate = self; //广播通知 self.nearbyServiceAdveriser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:peerID discoveryInfo:nil serviceType:@'rsp-receiver']; self.nearbyServiceAdveriser.delegate = self; [self.nearbyServiceAdveriser startAdvertisingPeer];这里有三个地方需要注意:
在初始化MCNearbyServiceAdvertiser 和MCNearbyServiceBrowser 对象时,传入的serviceType参数,这个参数必须满足:长度在1至15个字符之间,由ASCII字母、数字和“-”组成,论文,不能以“-”为开头或结尾,不能包含除了“-“之外的其他特殊字符,否则会报MCErrorInvalidParameter错误。
在监听广播通知时传入的参数serviceType必须与发送广播时传入的参数一致,否则无法监听到广播。
发送端和接收端创建的会话对象类型和加密方式等必须一致,否则无法收到对方的连接请求。
初始化完成就要处理两端之间相互交互的逻辑了,具体代码如下:
发送端:
这里需要注意:发出邀请有时间限制,当超出时限,接收端同意连接会报MCErrorTimedOut错误。
接收端:
当收到发送端的连接请求时,就应该关闭广播通知
至此,双方通信链路协商成功,开题报告,可以开始基于session向对方发送数据。
发送代码如下
发送端:
session提供了三种数据传输方式:普通数据传输(data)、数据流传输(streams)、数据源传输(resources),这里使用第三种,关于三种数据传输方式的使用及场景,可参考《 iOS--MultipeerConnectivity蓝牙通讯》。
这里有两个地方需要注意:
发送数据传入的resourceURL参数是文件在本地的路径,必须使用fileURLWithPath:创建,使用URLWithString:会报Unsupported resource type错误。
因为传输的文件可能是临时文件,所以传输完成需要移除临时文件,但这里传输完成不能马上移除本地文件,否则接收端会在文件接收快要完成时会出现localURL参数为空 报错为:Peer no longer connected,具体原因不明。
接收端:
// 数据源传输开始 - (void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress { NSLog(@'数据传输开始'); //KVO观察 self.progress = progress; [progress addObserver:self forKeyPath:@'completedUnitCount' options:NSKeyValueObservingOptionNew context:nil]; } // 数据传输完成回调 - (void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(nullable NSError *)error { if (error) { NSLog(@'数据传输结束%@----%@', localURL.absoluteString, error); }else { NSString *destinationPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:resourceName]; NSURL *destinationURL = [NSURL fileURLWithPath:destinationPath]; //转移文件 NSError *error1 = nil; if (![[NSFileManager defaultManager] moveItemAtURL:localURL toURL:destinationURL error:&error1]) { NSLog(@'移动文件出错:error = %@', error1.localizedDescription); }else { __weak typeof(self) ws = self; dispatch_async(dispatch_get_main_queue(), ^{ NSString *message = [NSString stringWithFormat:@'%@', resourceName]; UIAlertController *alert = [UIAlertController alertControllerWithTitle:@'文件接收成功' message:message preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *action = [UIAlertAction actionWithTitle:@'确定' style:UIAlertActionStyleDefault handler:nil]; [alert addAction:action]; [ws presentViewController:alert animated:YES completion:nil]; }); } } //移除监听 [self.progress removeObserver:self forKeyPath:@'completedUnitCount' context:nil]; } -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSProgress *progress = (NSProgress *)object; NSLog(@'%lf', progress.fractionCompleted); dispatch_async(dispatch_get_main_queue(), ^{ [self.receiverBtn setProgressValue:progress.fractionCompleted]; }); }至此,一次文件传输就已完成。
结尾#p#分页标题#e#这里使用的是MCNearbyServiceAdvertiser和MCNearbyServiceBrowser来进行节点连接,当然还可以使用MCAdvertiserAssistant和MCBrowserViewController来进行节点连接,因为后者系统封装了一套标准的UI界面,所以集成起来更加简单,这里就不再赘述。
最后奉上Demo地址