ios-App之间通信(App之间传数据)

如果需要在两个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参数:


屏幕快照 2018-12-27 下午12.05.42.png

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];
}

效果如下:


屏幕快照 2018-12-27 上午11.53.30.png

第五: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);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容