如果需要在两个app之间做数据传输,ios常用的app之间通信方式有五种:urlscheme、keychain钥匙串、UIPasteboard剪切板、UIDocumentInteractionController文件共享、localsocket本地长链接服务。详细如下。
第一: URL Scheme
url scheme是ios常用的通信方式,appA通过apenurl的方法跳转到appB,并且可以在url上拼接上想要的参数。如下:
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"testB://"] options:nil completionHandler:^(BOOL success) {
}];
其中@"testB://" 为被调起的appB的info中配置的URL-Types参数:
appB被调起之后会执行下面的代理方法:
Application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
其中可以通过options[UIApplicationOpenURLOptionsSourceApplicationKey]获取到对方的bundleID,也可以获取其他的信息。
注意:如果不是使用URL Scheme打开的appB,而是使用appB的bundleID打开的appB,appB是不会走openURL这个代理方法的。
第二:keychain
keychain相当于MAC OS的钥匙串,可以存储一些敏感信息,比如用户ID、密码等等,keychain的安全机制保证这些数据不会被窃取。
keychain的数据并不放在沙盒目录中,即使删除了APP,依然可以获取保存过的数据,关于keychain的增删改查,可以参考:iOS keychain理解
相同开发这账号且bundleId前缀相同:
App之间可以通过keychain来实现信息共享,但是这仅仅限制于,同一个开发者账号,并且两个工程的bundleID的前缀必须是相同的,才能够共享keychain的数据。
相同开发者账号创建keychain共享数据可以参考:iOS 开发keychain 使用与多个APP之间共享keychain数据的使用
如果两个App不是同一个账号,当然就不能够使用这种方式进行App通信拉.
第三:UIPasteboard
UIPasteboard就是通过剪切板实现App之间的通信。App A 把需要存放的数据通过约定的key保存在剪贴板之中,App B再通过约定的key去获取。
相同开发者账号且bundleId前缀相同:
这里与keychain是异曲同工的,只有AppID的prefix是相同的,才能够创建自己的剪切板。
//创建方法:
-(void)saveToMyPasteboard
{
UIPasteboard * myPasteboard = [UIPasteboard pasteboardWithName:@"FAYE_PASTE" create:YES];
if (_data) {
//存储
[myPasteboard setData:_data forPasteboardType:KEY_MyPasteboard];
}
if ([myPasteboard containsPasteboardTypes:@[KEY_MyPasteboard]]) {
//取
NSData * myData = [myPasteboard dataForPasteboardType:KEY_MyPasteboard];
NSString * myString = [[NSString alloc] initWithData:myData encoding:NSUTF8StringEncoding];
NSLog(@"myString=========%@",myString);
}
}
上面只是一个简单的例子,可以自己去xcode中查询有关的API,取自己需要的调用。
不同开发者账号
不同开发者账号,只能够使用系统的UIPasteboard,[UIPasteboard generalPasteboard]不能够创建自己的剪切板偶,可以约定好key,去剪切板存值取值。
-(void)saveToGeneralPasteboard
{
//存
if (_data) {
[[UIPasteboard generalPasteboard]setData:_data forPasteboardType:KEY_GeneralPasteboard];
}
//取
if ([[UIPasteboard generalPasteboard]containsPasteboardTypes:@[KEY_GeneralPasteboard]]) {
NSData * generalData = [[UIPasteboard generalPasteboard]dataForPasteboardType:KEY_GeneralPasteboard];
NSString * generalString = [[NSString alloc] initWithData:generalData encoding:NSUTF8StringEncoding];
NSLog(@"generalString=======%@",generalString);
}
}
这只是简单的栗子,详细的可以自己查看系统API。
注意: 这里通过剪切板传值,笔者是用系统剪切板传值,不能够去多个key存储,会被覆盖掉,就像文本编辑的粘贴复制一样,只能够去取到最新一次存储的。 不过一般在打开应用的时候才会存进去,另一个应用马上被打开后就获取到数据,所以一般也不存在数据被覆盖,毕竟时间够短,打开一瞬间,只要不在自己的代码中多次存储,我认为没什么太大问题的。
更多的剪切板用法自行百度哈。
第四:UIDocumentInteractionController
UIDocumentInteractionController它主要是用来实现同设备上app之间的共享文档,例如文档的预览、打印、发邮件和复制等基本的一些功能。使用起来也是简单.首先是通过调用它唯一的类方法interactionControllerWithURL:,然后是传入一个URL(NSURL),为你想要共享的文件来初始化一个实例对象。然后UIDocumentInteractionControllerDelegate,最后是显示菜单和预览窗口。
demo如下:
@property (nonatomic, strong) UIDocumentInteractionController *shareController;
- (void)openWithOtherApp
{
// 第三方分享
NSString *filePath = [/*这里是文件路径*/];
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
self.shareController = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
[self.shareController presentOptionsMenuFromRect:self.view.frame inView:self.view animated:YES];
}
效果如下:
第五:localsocket
原理,其实很简单,一个App在本地的端口进行TCP的bind和listen,另外一个App在本地同一个端口进行connect,这样就建立了一个正常的TCP连接。相当于,一个app为服务端,另一个app为客户端,通过长链接发送和接收数据。代码如下:
头文件:
//socket
#import <sys/socket.h>
#import <netinet/in.h>
#import <unistd.h>
#import <netinet/tcp.h>
//ip
#include <ifaddrs.h>
#include <arpa/inet.h>
#include <net/if.h>
#define IOS_CELLULAR @"pdp_ip0"
#define IOS_WIFI @"en0"
#define IP_ADDR_IPv4 @"ipv4"
#define IP_ADDR_IPv6 @"ipv6"
1.创建服务端--appA:
创建socket
@property (nonatomic ,assign) int newSock;
@property (nonatomic ,assign) int sock;
//local socket
-(void)startSocket{
//1,首先用socket()函数创建一个套接字
// socket返回一个int值 -1代表创建失败。 第一个参数指定了 协议簇 ,第二个参数指定了套接口类型 ,第三个接口指定相应的传输协议。
int sock = socket(AF_INET , SOCK_STREAM,0);
if (sock == -1) {
close(sock); //服务端错误
return;
}
self.sock = sock;
//2,绑定本机的地址和端口号
struct sockaddr_in sockAddr; // 地址结构体数据 记录ip和端口号
sockAddr.sin_family = AF_INET;//声明使用的协议
const char *ip = [[self getIPAddress:YES] cStringUsingEncoding:NSASCIIStringEncoding];// 获取本机的ip,转换成char类型的
sockAddr.sin_addr.s_addr = inet_addr(ip);// 将ip赋值给结构体,inet_addr()函数是将一个点分十进制的IP转换成一个长整数型数
sockAddr.sin_port = htons(12345);// 设置端口号,htons()是将整型变量从主机字节顺序转变成网络字节顺序
/*
* bind函数用于将套接字关联一个地址,返回一个int值,-1为失败
* 第一个参数指定套接字,就是前面socket函数调用返回额套接字
* 第二个参数为指定的地址
* 第三个参数为地址数据的大小
*/
int bd = bind(sock,(struct sockaddr *) &sockAddr, sizeof(sockAddr));
if(bd == -1){
close(sock);
NSLog(@"bind error : %d",bd);
return;
}
//3.监听绑定的地址 --长链接
/*
* listen函数使用主动连接套接接口变为被连接接口,使得可以接受其他进程的请求,返回一个int值,-1为失败
* 第一个参数是之前socket函数返回的套接字
* 第二个参数可以理解为连接的最大限制
*/
int ls = listen(sock,20);
if(ls == -1){
close(sock);
NSLog(@"listen error : %d",ls);
return;
}
NSLog(@"服务端开启成功");
//4.下面就是等待客户端的连接,使用accept()(由于accept函数会阻塞线程,在等待连接的过程中会一直卡着,所以建议将其放在子线程里面)
// 4.1,开启一个子线程
NSThread *recvThread = [[NSThread alloc]initWithTarget:self selector:@selector(recvData) object:nil];
[recvThread start];
}
长链接 接收数据
- (void)recvData{
//4.2,等待客户端连接
// 声明一个地址结构体,用于后面接收客户端返回的地址
struct sockaddr_in recvAddr;
// 地址大小
socklen_t recv_size =sizeof(struct sockaddr_in);
/*
* accept()函数在连接成功后会返回一个新的套接字(self.newSock),用于之后和这个客户端之前收发数据
* 第一个参数为之前监听的套接字,之前是局部变量,现在需要改为全局的* 第二个参数是一个结果参数,它用来接收一个返回值,这个返回值指定客户端的地址
* 第三个参数也是一个结果参数,它用来接收recvAddr结构体的代销,指明其所占的字节数
*/
self.newSock = accept(self.sock,(struct sockaddr *) &recvAddr,&recv_size);
// 3,来到这里就代表已经连接到一个新的客户端,下面就可以进行收发数据了,主要用到了send()和recv()函数
ssize_t bytesRecv = -1;// 返回数据字节大小
char recvData[128] ="";// 返回数据缓存区
// 如果一端断开连接,recv就会马上返回,bytesrecv等于0,然后while循环就会一直执行,所以判断等于0是跳出去
while(YES){
bytesRecv = recv(self.newSock,recvData,128,0);
// recvData为收到的数据
NSLog(@"%zd %s",bytesRecv,recvData);
if(bytesRecv == 0){
break;
}
}
}
长链接 下发数据
//5.下发数据
- (void)sendMessage{
char sendData[32] ="hello client";
ssize_t size_t = send(self.newSock, sendData,strlen(sendData), 0);
}
辅助方法 获取IP
//获取设备当前网络IP地址
- (NSString *)getIPAddress:(BOOL)preferIPv4
{
NSArray *searchArray = preferIPv4 ?
@[ /*IOS_VPN @"/" IP_ADDR_IPv4, IOS_VPN @"/" IP_ADDR_IPv6,*/ IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] :
@[ /*IOS_VPN @"/" IP_ADDR_IPv6, IOS_VPN @"/" IP_ADDR_IPv4,*/ IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4 ] ;
NSDictionary *addresses = [self getIPAddresses];
NSLog(@"addresses: %@", addresses);
__block NSString *address;
[searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop)
{
address = addresses[key];
if(address) *stop = YES;
} ];
return address ? address : @"0.0.0.0";
}
//获取所有相关IP信息
- (NSDictionary *)getIPAddresses
{
NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];
// retrieve the current interfaces - returns 0 on success
struct ifaddrs *interfaces;
if(!getifaddrs(&interfaces)) {
// Loop through linked list of interfaces
struct ifaddrs *interface;
for(interface=interfaces; interface; interface=interface->ifa_next) {
if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) {
continue; // deeply nested code harder to read
}
const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {
NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
NSString *type;
if(addr->sin_family == AF_INET) {
if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
type = IP_ADDR_IPv4;
}
} else {
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;
if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
type = IP_ADDR_IPv6;
}
}
if(type) {
NSString *key = [NSString stringWithFormat:@"%@/%@", name, type];
addresses[key] = [NSString stringWithUTF8String:addrBuf];
}
}
}
// Free memory
freeifaddrs(interfaces);
}
return [addresses count] ? addresses : nil;
}
2.创建客户端--appB:
-(void)startClientSocket{
//1.和服务端一样用socket函数。创建套接字**
int sock = socket(AF_INET, SOCK_STREAM,0);
if(sock == -1){
NSLog(@"socket error : %d",sock);
return;
}
self.sock = sock;
//2.获取主机的地址。绑定本机地址和端口号
NSString *host = [self getIPAddress:YES];// 获取本机ip地址
// 返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针
struct hostent *remoteHostEnt = gethostbyname([host UTF8String]);
if(remoteHostEnt == NULL){
close(sock);
NSLog(@"无法解析服务器主机名");
return;
}
// 配置套接字将要连接主机的ip地址和端口号,用于connect()函数
struct in_addr *remoteInAddr = (struct in_addr *)remoteHostEnt->h_addr_list[0];
struct sockaddr_in socktPram;
socktPram.sin_family = AF_INET;
socktPram.sin_addr = *remoteInAddr;
socktPram.sin_port = htons(12345);
//3,使用connect()函数连接主机
/*
* connect函数通常用于客户端简历tcp连接,连接指定地址的主机,函数返回一个int值,-1为失败
* 第一个参数为socket函数创建的套接字,代表这个套接字要连接指定主机
* 第二个参数为套接字sock想要连接的主机地址和端口号
* 第三个参数为主机地址大小
*/
int con = connect(sock, (struct sockaddr *) &socktPram, sizeof(socktPram));
if(con == -1){
close(sock);
NSLog(@"连接失败");
return;
}
NSLog(@"连接成功"); // 来到这代表连接成功;
//4.下面就是等待客户端的连接,使用accept()(由于accept函数会阻塞线程,在等待连接的过程中会一直卡着,所以建议将其放在子线程里面)
// 4.1,开启一个子线程
NSThread *recvThread = [[NSThread alloc]initWithTarget:self selector:@selector(recvData) object:nil];
[recvThread start];
}
client接收数据:
- (void)recvData{
// 接受数据,放在子线程
ssize_t bytesRecv = -1;
char recvData[32] = "";
while (1) {
bytesRecv = recv(self.sock, recvData, 32, 0);
NSLog(@"%zd %s",bytesRecv,recvData);
if (bytesRecv == 0) {
break;
}
}
}
client发送数据:
- (void)senddata {
// 发送数据
char sendData[32] = "hello service";
ssize_t size_t = send(self.sock, sendData, strlen(sendData), 0);
NSLog(@"%zd",size_t);
}