標籤:越獄開發 通過 系統 returns 類型 htm gid ret encoding
iOS處理序間通訊之CFMessagePort
iOS系統是出了名的封閉,每個應用的活動範圍被嚴格地限制在各自的沙箱中。儘管如此,iOS還是提供了若干處理序間通訊機制,CFMessagePort就是其中之一。
從類名可以看出,CFMessagePort屬於Core Foundation層的東西,其實現部分是開源的,代碼在可以在蘋果的開原始碼庫中找到。
使用方式
1、訊息接收者
CFMessagePort連接埠訊息的接收者需要實現以下功能:
1.1 註冊監聽
訊息接收者需要通過以下方式註冊訊息監聽:
-(void)startListenning{ if (0 != mMsgPortListenner && CFMessagePortIsValid(mMsgPortListenner)) { CFMessagePortInvalidate(mMsgPortListenner); } mMsgPortListenner = CFMessagePortCreateLocal(kCFAllocatorDefault,CFSTR(LOCAL_MACH_PORT_NAME),onRecvMessageCallBack, NULL, NULL); CFRunLoopSourceRef source = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, mMsgPortListenner, 0); CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes); NSLog(@"start listenning");}
其中LOCAL_MACH_PORT_NAME的定義為:
#define LOCAL_MACH_PORT_NAME "com.wangzz.demo"
經過查看源碼發現,CFMessagePort實際上是通過mach port實現的。Mach port是iOS系統提供的基於連接埠的輸入源,可用於線程或進程間通訊。而Runloop支援的輸入源類型中就包括基於連接埠的輸入源,因此可以使用Runloop做為CFMessagePort連接埠源事件的監聽者。
上述代碼有幾點需要說明:
通過CFMessagePortCreateLocal可以建立一個本地CFMessagePortRef對象
CFMessagePort對象是靠一個字串來唯一標識的,這一點非常重要,在這裡字串是由宏LOCAL_MACH_PORT_NAME定義的;
建立CFMessagePort對象的同時設定了連接埠源事件的回呼函數onRecvMessageCallBack,用於處理連接埠源事件;
將建立的對象作為輸入源添加到Runloop中,從而實現對連接埠源事件的監聽,當Runloop收到對應的連接埠源事件時,會調用上一步中指定的回調方法;
1.2 實現回調方法
回呼函數為CFMessagePortCallBack類型,其定義部分為:
typedef CFDataRef (*CFMessagePortCallBack) ( CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info);
各個參數的含義為:
當前接收訊息的CFMessagePortRef對象。
這個欄位非常有用,用於標識訊息。如果通訊雙方進程約定號每個msgid對應的資料結構,即可實現較為複雜的通訊。
通訊的真正資料部分。
為使用CFMessagePortCreateLocal方法建立port連接埠時指定的CFMessagePortContext對象的info欄位,通常為空白。
該回調方法可以返回一個CFDataRef類型的資料給port訊息的寄件者,以實現有效雙方通訊,這一點也非常重要。
我的回呼函數onRecvMessageCallBack的實現:
CFDataRef onRecvMessageCallBack(CFMessagePortRef local,SInt32 msgid,CFDataRef cfData, void*info){ NSLog(@"onRecvMessageCallBack is called"); NSString *strData = nil; if (cfData) { const UInt8 * recvedMsg = CFDataGetBytePtr(cfData); strData = [NSString stringWithCString:(char *)recvedMsg encoding:NSUTF8StringEncoding]; /** 實現資料解析操作 **/ NSLog(@"receive message:%@",strData); } //為了測試,產生返回資料 NSString *returnString = [NSString stringWithFormat:@"i have receive:%@",strData]; const char* cStr = [returnString UTF8String]; NSUInteger ulen = [returnString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; CFDataRef sgReturn = CFDataCreate(NULL, (UInt8 *)cStr, ulen); return sgReturn;}
該方法實現的較為簡單,解析約定的資料(測試代碼中約定傳送的是string),為了測試,同時產生一個CFDataRef資料返回給port訊息的寄件者。
1.3 取消連接埠監聽
可以通過如下方式取消對port連接埠的監聽:
- (void)endLisenning{ CFMessagePortInvalidate(mMsgPortListenner); CFRelease(mMsgPortListenner);}
CFMessagePortInvalidate會停止port訊息的發送和接收操作,而只有調用了CFRelease,CFMessagePortRef對象才真正的被釋放掉。
2、訊息寄件者
發送部分代碼如下:
-(NSString *)sendMessageToDameonWith:(id)msgInfo msgID:(NSInteger)msgid{ // 產生Remote port CFMessagePortRef bRemote = CFMessagePortCreateRemote(kCFAllocatorDefault, CFSTR(MACH_PORT_REMOTE)); if (nil == bRemote) { NSLog(@"bRemote create failed"); return nil; } // 構建發送資料(string) NSString *msg = [NSString stringWithFormat:@"%@",msgInfo]; NSLog(@"send msg is :%@",msg); const char *message = [msg UTF8String]; CFDataRef data,recvData = nil; data = CFDataCreate(NULL, (UInt8 *)message, strlen(message)); // 執行發送操作 CFMessagePortSendRequest(bRemote, msgid, data, 0, 100 , kCFRunLoopDefaultMode, &recvData); if (nil == recvData) { NSLog(@"recvData date is nil."); CFRelease(data); CFMessagePortInvalidate(bRemote); CFRelease(bRemote); return nil; } // 解析返回資料 const UInt8 * recvedMsg = CFDataGetBytePtr(recvData); if (nil == recvedMsg) { NSLog(@"receive date err."); CFRelease(data); CFMessagePortInvalidate(bRemote); CFRelease(bRemote); return nil; } NSString *strMsg = [NSString stringWithCString:(char *)recvedMsg encoding:NSUTF8StringEncoding]; NSLog(@"%@",strMsg); CFRelease(data); CFMessagePortInvalidate(bRemote); CFRelease(bRemote); CFRelease(recvData); return strMsg;}
其中MACH_PORT_REMOTE的定義為:
#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連接埠肯定是失敗的。
說明
在使用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.
iOS系統多任務機制,使得處理序間通訊基本都只能用于越獄開發。常用的情境是前端有一個UI程式用於介面展示,後端有一個daemo精靈程式用於任務處理。
demo工程
特地做了了個demo工程,以便更好地示範CFMessagePort的使用,可以到CSDN下載。
為了類比處理序間通訊情境,我將訊息接收進程CFMessagePortReceive做成了能夠後台播放音樂的程式,以便其切到後台後能繼續存活。
由於CFMessagePort不再支援iOS7及以後系統,本demo實在iOS6系統上測試的。
demo使用方式:
CFMessagePortReceive啟動後,點擊Start Listenning建立CFMessagePort介面並開始監聽port訊息,然後將CFMessagePortReceive切到後台;
啟動CFMessagePortSend程式,在輸入框中寫入內容,點擊發送按鈕即可和CFMessagePortReceive通訊。
MessagePort通訊過程中會有日誌輸出,可以使用以下方式查看日誌:
1.真機
選擇:Xcode->Window->Organizer->Devices,然後選中視窗左側當前裝置的Console視窗查看。
2.模擬器
選擇:模擬器->調試->開啟系統日誌,或者直接使用快速鍵?/直接開啟系統控制台查看日誌。
參考文檔
iOS處理序間通訊之CFMessagePort