IOS——进程通讯

Mac OS下的IPC方式种类很多,大约有下面几种。
1. Mach API
2. CFMessagePort
3. Distributed Objects (DO)
4. Apple events
5. UNIX domain sockets
6. Internet sockets
7. Memory Mapping

第一种太底层,很少有人用苹果也不推荐
第2,3,4种是苹果提供的较为高层的通讯机制
第5,6种大家应该都知道,用sockets方法离散度,移植性更好。5和6有一些区别,5使用unix文件系统作为通讯媒介,可以使用unix文件权限系统做通讯限制,它的另外一个名字就叫ipc sokets,6可以做机器间通讯做本地ipc也可以,不过要额外做一些努力。
第7种... 有把握用的人不多(我最近也在看这个)。

我想以上一些够你选择了

另外,我可以说一下我的选择
1. 假如你的进程间通讯不频繁,只是发送一些异步信号,DO是很好的选择,也就是楼上有人说得NSDistributedNotificationCenter
2. 如果你的进程间通讯频繁,但数据量不大,需要响应度高,domain sockets很好(DO也许也能满足,你自己测试测试)

3. 最后如果进程间通讯频繁,数据流巨大例如音频,图片等,需要real-time级别的, 选择Memory Mapping, 缺点是这个技术复杂需要自己做内存paging管理。


iOS进程间通信之CFMessagePort

iOS系统是出了名的封闭,每个应用的活动范围被严格地限制在各自的沙盒中。尽管如此,iOS还是提供了若干进程间通信机制,CFMessagePort就是其中之一。
从类名可以看出,CFMessagePort属于Core Foundation层的东西,其实现部分是开源的,代码在可以在苹果的 开源代码库中找到。
使用方式
1、消息接收者
CFMessagePort端口消息的接收者需要实现以下功能:
1.1 注册监听
消息接收者需要通过以下方式注册消息监听:
 
 
  1. -(void)startListenning
  2. {
  3. if (0 != mMsgPortListenner && CFMessagePortIsValid(mMsgPortListenner))
  4. {
  5. CFMessagePortInvalidate(mMsgPortListenner);
  6. }
  7. mMsgPortListenner = CFMessagePortCreateLocal(kCFAllocatorDefault,CFSTR(LOCAL_MACH_PORT_NAME),onRecvMessageCallBack, NULL, NULL);
  8. CFRunLoopSourceRef source = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, mMsgPortListenner, 0);
  9. CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  10. NSLog(@"start listenning");
  11. }
其中LOCAL_MACH_PORT_NAME的定义为:
 
 
  1. #define LOCAL_MACH_PORT_NAME "com.wangzz.demo"
经过查看源码发现,CFMessagePort实际上是通过mach port实现的。Mach port是iOS系统提供的基于端口的输入源,可用于线程或进程间通讯。而Runloop支持的输入源类型中就包括基于端口的输入源,因此可以使用Runloop做为CFMessagePort端口源事件的监听者。
上述代码有几点需要说明:
1. 通过CFMessagePortCreateLocal可以创建一个本地CFMessagePortRef对象
2. CFMessagePort对象是靠一个字符串来唯一标识的,这一点非常重要,在这里字符串是由宏LOCAL_MACH_PORT_NAME定义的;
3. 创建CFMessagePort对象的同时设置了端口源事件的回调函数onRecvMessageCallBack,用于处理端口源事件;
4. 将创建的对象作为输入源添加到Runloop中,从而实现对端口源事件的监听,当Runloop收到对应的端口源事件时,会调用上一步中指定的回调芳芳;
1.2 实现回调方法
回调函数为CFMessagePortCallBack类型,其定义部分为:
 
 
  1. typedef CFDataRef (*CFMessagePortCallBack) (
  2. CFMessagePortRef local,
  3. SInt32 msgid,
  4. CFDataRef data,
  5. void *info
  6. );
各个参数的含义为:
CFMessagePortRef local:当前接收消息的CFMessagePortRef对象。
SInt32 msgid:这个字段非常有用,用于标识消息。如果通信双方进程约定号每个msgid对应的数据结构,即可实现较为复杂的通信。
CFDataRef data:通信的真正数据部分。
void *info:为使用CFMessagePortCreateLocal方法创建port端口时指定的CFMessagePortContext对象的info字段,通常为空。
该回调方法可以返回一个CFDataRef类型的数据给port消息的发送者,以实现有效的双方通信,这一点也非常重要。
我的回调函数onRecvMessageCallBack的实现:
 
 
  1. CFDataRef onRecvMessageCallBack(CFMessagePortRef local,SInt32 msgid,CFDataRef cfData, void*info)
  2. {
  3. NSLog(@"onRecvMessageCallBack is called");
  4. NSString *strData = nil;
  5. if (cfData)
  6. {
  7. const UInt8 * recvedMsg = CFDataGetBytePtr(cfData);
  8. strData = [NSString stringWithCString:(char *)recvedMsg encoding:NSUTF8StringEncoding];
  9. /**
  10. 实现数据解析操作
  11. **/
  12. NSLog(@"receive message:%@",strData);
  13. }
  14. //为了测试,生成返回数据
  15. NSString *returnString = [NSString stringWithFormat:@"i have receive:%@",strData];
  16. const char* cStr = [returnString UTF8String];
  17. NSUInteger ulen = [returnString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  18. CFDataRef sgReturn = CFDataCreate(NULL, (UInt8 *)cStr, ulen);
  19. return sgReturn;
  20. }
该方法实现的较为简单,解析约定的数据(测试代码中约定传送的是string),为了测试,同时生成一个CFDataRef数据返回给port消息的发送者。
1.3 取消端口监听
可以通过如下方式取消对port端口的监听:
 
 
  1. - (void)endLisenning
  2. {
  3. CFMessagePortInvalidate(mMsgPortListenner);
  4. CFRelease(mMsgPortListenner);
  5. }
CFMessagePortInvalidate会停止port消息的发送和接收操作,而只有调用了CFRelease,CFMessagePortRef对象才真正的被释放掉。
2、消息发送者
发送部分代码如下:
 
 
  1. -(NSString *)sendMessageToDameonWith:(id)msgInfo msgID:(NSInteger)msgid
  2. {
  3. // 生成Remote port
  4. CFMessagePortRef bRemote = CFMessagePortCreateRemote(kCFAllocatorDefault, CFSTR(MACH_PORT_REMOTE));
  5. if (nil == bRemote) {
  6. NSLog(@"bRemote create failed");
  7. return nil;
  8. }
  9. // 构建发送数据(string)
  10. NSString *msg = [NSString stringWithFormat:@"%@",msgInfo];
  11. NSLog(@"send msg is :%@",msg);
  12. const char *message = [msg UTF8String];
  13. CFDataRef data,recvData = nil;
  14. data = CFDataCreate(NULL, (UInt8 *)message, strlen(message));
  15. // 执行发送操作
  16. CFMessagePortSendRequest(bRemote, msgid, data, 0, 100 , kCFRunLoopDefaultMode, &recvData);
  17. if (nil == recvData) {
  18. NSLog(@"recvData date is nil.");
  19. CFRelease(data);
  20. CFMessagePortInvalidate(bRemote);
  21. CFRelease(bRemote);
  22. return nil;
  23. }
  24. // 解析返回数据
  25. const UInt8 * recvedMsg = CFDataGetBytePtr(recvData);
  26. if (nil == recvedMsg) {
  27. NSLog(@"receive date err.");
  28. CFRelease(data);
  29. CFMessagePortInvalidate(bRemote);
  30. CFRelease(bRemote);
  31. return nil;
  32. }
  33. NSString *strMsg = [NSString stringWithCString:(char *)recvedMsg encoding:NSUTF8StringEncoding];
  34. NSLog(@"%@",strMsg);
  35. CFRelease(data);
  36. CFMessagePortInvalidate(bRemote);
  37. CFRelease(bRemote);
  38. CFRelease(recvData);
  39. return strMsg;
  40. }
其中MACH_PORT_REMOTE的定义为:
 
 
  1. #define MACH_PORT_REMOTE "com.wangzz.demo"
发送消息时要相对简单,首先通过CFMessagePortCreateRemote生成一个Remote的CFMessagePortRef,这里需要注意的是CFMessagePortCreateRemote时传入的字符串唯一标识MACH_PORT_REMOTE必须和消息接收者创建local的CFMessagePortRef时使用的字符串唯一标识是同一个!
通过查看源码发现,CFMessagePortCreateRemote会根据MACH_PORT_REMOTE定义的字符串为唯一标识获取消息接收者通过CFMessagePortCreateLocal使用相同字符串创建的底层mach port端口,从而实现向消息接收者发送信息。
如果消息接收者还没有创建或者通过CFMessagePortCreateLocal创建local端口失败时,想要通过CFMessagePortCreateRemote去创建remote端口肯定是失败的。
说明
1. 很遗憾的是,在iOS7及以后系统中,CFMessagePort的通信机制不再可用。
在使用CFMessagePortCreateLocal/CFMessagePortCreateRemote创建CFMessagePortRef对象时会失败,官方文档中是这么说的:
This method is not available on iOS 7 and later—it will return NULL and log a sandbox violation in syslog. See Concurrency Programming Guide for possible replacement technologies.
2. CFMessagePort只能用于本地进程通信。
3. CFMessagePort是基于mach port端口的通信方式,不但可以用于进程通信,也可以用于线程间通信,只是线程间通信有了GCD和Cocoa提供的原生方法,已经能很方便的实现了,没必要再使用CFMessagePort。
4. 进程通信使用场景
iOS系统多任务机制,使得进程间通信基本都只能用于越狱开发。常用的场景是前端有一个UI程序用于界面展示,后端有一个daemo精灵程序用于任务处理。
demo工程
特地做了了个demo工程,以便更好地演示CFMessagePort的使用,可以到 CSDN下载
为了模拟进程间通信场景,我将消息接收进程CFMessagePortReceive做成了能够后台播放音乐的程序,以便其切到后台后能继续存活。
由于CFMessagePort不再支持iOS7及以后系统,本demo实在iOS6系统上测试的。
demo使用方式
1. CFMessagePortReceive启动后,点击Start Listenning创建CFMessagePort接口并开始监听port消息,然后将CFMessagePortReceive切到后台;
2. 启动CFMessagePortSend程序,在输入框中写入内容,点击发送按钮即可和CFMessagePortReceive通信。
3. MessagePort通信过程中会有日志输出,可以使用以下方式查看日志:
1. 真机
选择:Xcode->Window->Organizer->Devices,然后选中窗口左侧当前设备的Console窗口查看。
2. 模拟器
选择:模拟器->调试->打开系统日志,或者直接使用快捷键?/直接打开系统控制台查看日志。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值