美文网首页iOS开发之常用技术点
ios-App之间通信(App之间传数据)

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

作者: revivefsd | 来源:发表于2018-12-27 10:43 被阅读18次

如果需要在两个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);
}

相关文章

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

    如果需要在两个app之间做数据传输,ios常用的app之间通信方式有五种:urlscheme、keychain钥匙...

  • App之间的通信

    什么是 URL schemes ?iOS began supporting URL schemes a coupl...

  • Flutter与Native数据交互,MethodChannel

    平台通道 Flutter APP同iOS&Android native之间的数据通信如下图所示: 平台信道数据类型...

  • AppleWatch

    相关链接App与Extension之间的通信

  • iOS App之间的通信及APP之间的数据传输

    云峰小罗(yunfengwuge) 大神的文章! https://www.jianshu.com/p/d640...

  • iOS app之间通信方式

    URL Scheme Keychain UIPasteboard UIActivityViewController...

  • React 笔记摘要

    父子组件数据通信 父子组件之间的数据通信细分其实还有两种:父与子之间和子与父之间。 在React中,父与子之间的数...

  • Vue组件通信

    Vue 组件之间的通信,通常我们遇到的都是父子组件之间的通信 一、父子组件传参 子组件定义 Props 属性; 父...

  • 2018-09-25组件

    组件分为:子传父父传子非父子之间的通信 1.子传父 2.父传子 3.非父子之间传值

  • 面试题3

    1.Surfaceview 2通常Activity之间的通信有三种方式:单向不传参数通信、单项传参数通信和双向通信...

网友评论

    本文标题:ios-App之间通信(App之间传数据)

    本文链接:https://www.haomeiwen.com/subject/bqdvxqtx.html