iOS進(jìn)程間通信體驗

最近做了一遍基于Replaykit Extension實現(xiàn)iOS錄屏直播的需求闰挡,其中宿主App和Extension間用到了CFMessagePort進(jìn)行進(jìn)程間通信椅棺,記錄一下瞄桨,并試圖封裝出一個小輪子(doing)

有啥用

已經(jīng)使用:

  • 錄屏直播場景下,主App起定時器通過port時不時ping一下ReplaykitExtension是否在運行宁玫,沒有的話提示開啟
  • ReplaykitExtension時不時ping一下主App控制臺是否在選擇背景圖固惯,如果是,則推流默認(rèn)圖片洛心,保護(hù)用戶相冊隱私
  • 結(jié)合UserDefault固耘,在需要數(shù)據(jù)同步時發(fā)送message通知另一方去更新數(shù)據(jù)

設(shè)想:

  • 部分場景下讓同一個AppGroup下的另一個后臺App/Extension停止播放/數(shù)據(jù)更新等
  • 與同一個AppGroup下的另一個App/Extension進(jìn)行數(shù)據(jù)交互,(token共享词身、偏好共享等)

通信的前提條件

  1. 兩個通信進(jìn)程需要在同一個AppGroup中
  2. CFMessagePortRef的portName必須是以 AppGroupId為前綴厅目,
    例如com.clc.group.portName,其中com.clc.group是AppGroupId
  3. 如果是一個前臺一個后臺兩個進(jìn)程,需要后臺彼鸱螅活(Replaykit不必葫笼,沒有后臺狀態(tài))

結(jié)構(gòu)是怎樣的

先貼一下思考的時候畫的圖(一邊想一變畫的,大概意思就是兩者通過MessagePort通信)


image.png

如圖拗馒,左邊是Extension進(jìn)程路星,右邊是App進(jìn)程。
通過在Runloop上的Source1位置掛上一個Listener(即一個Port)作為接收端诱桂,在漫長的跑圈里奥额,等待Port來訪。


創(chuàng)建Listener

- (void)startListener
    CFStringRef portName = (__bridge CFStringRef)(self.PortName);
    //回調(diào)
    CFMessagePortCallBack callback = onRecvMessageCallBack;
    //創(chuàng)建端口
    CFMessagePortRef port = CFMessagePortCreateLocal(kCFAllocatorDefault, portName, callback, NULL, NULL);
    //端口封裝成source
    CFRunLoopSourceRef source = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, port, 0);
    //source掛上runloop
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
}

再寫一下間聽到信息時的回調(diào)函數(shù)

CFDataRef onRecvMessageCallBack(CFMessagePortRef local ,SInt32 msgid,CFDataRef cfData, void*info)
{
        //偽代碼
        //處理收到的data
        handler(cfData);
        //準(zhǔn)備好伴手禮數(shù)據(jù)回調(diào)給發(fā)送者
        return responseData;
}

那么一個Listener就創(chuàng)建完成了访诱,那么接下來讓我們打開這個Listener

CLCListener *listener = [[CLCListener alloc] initWithPortName:portName];
//開啟listener
NSThread *listenerThread = [[NSThread alloc] initWithBlock:^{
    [listener startListener];
    CFRunLoopRun();
}];
listener.runingThread = listenerThread;
[listenerThread start];

OK垫挨,那么接收端這邊就準(zhǔn)備好了,需要注意的是

發(fā)送端發(fā)送-->Listener收到信息進(jìn)入回調(diào)函數(shù)-->完成回調(diào)給發(fā)送放

這一整個流程都是同步進(jìn)行的触菜,所以如果在回調(diào)函數(shù)里避免耗時操作九榔。
如果不可避免,則應(yīng)先進(jìn)行回調(diào)涡相,再想辦法在處理完成后進(jìn)行一次通信(比如發(fā)送方開一個Listener等待哲泊,處理方完成后主動通知發(fā)送方,發(fā)送方收到后再關(guān)閉用于等待處理的Listener)


進(jìn)行一次美好的調(diào)用

讓我們看看發(fā)送方是怎么向Listener發(fā)送信息的催蝗,
首先這里會用CFMessagePortCreateRemote創(chuàng)建一個Port切威。
其中的portName就是和上面創(chuàng)建Listener時的portName。

- (void)doRequestWithMessage:(NSString *)message
                portName:(NSString *)portName
                 timeOut:(NSTimeInterval)timeout
                callBack:(void(^)(id data))callBack 
  CFStringRef cPortName = (__bridge CFStringRef)(portName);
  // 生成Remote port
  CFMessagePortRef port = CFMessagePortCreateRemote(kCFAllocatorDefault, cPortName);

如果在創(chuàng)建的時候丙号,Listener還沒有被創(chuàng)建先朦,那么這里返回的port就是nil。

 if (nil == port) {
    callBack(nil);
    return;
}

如果有值犬缨,那么槍就準(zhǔn)備好了喳魏,接下來上彈

//帶過去的參數(shù)
const char *cMessage = [message UTF8String];
CFDataRef cRequestData = CFDataCreate(NULL, (UInt8 *)cMessage, strlen(cMessage));
//用來接回調(diào)的CFDataRef
CFDataRef cResponseData = nil;

//demo里隨便寫的,可以是任意數(shù)值怀薛,在接受方可以取到
SInt32 msgId = 32; 

//do localRequest
CFMessagePortSendRequest(port, msgId, cRequestData, 1, timeout, kCFRunLoopDefaultMode, &cResponseData);

其中的1和timeout一個是發(fā)送timeout刺彩,一個是等待回調(diào)的timeout,還是上面那句話枝恋,因為這里是同步調(diào)用创倔,所以需要小心耗時操作阻塞

如果調(diào)用成功,那么這里的cResponseData已經(jīng)把值帶回來了

把CF對象橋過去給NS對象焚碌,就可以進(jìn)業(yè)務(wù)邏輯了

if (callBack) {
    //cf to ns
    NSData* response = (__bridge_transfer NSData*)cResponseData;
    callBack(response);
}

通信完成畦攘,走的時候帶一下垃圾,歡迎下次光臨

    //clean up
    CFRelease(cRequestData);
    CFMessagePortInvalidate(port);
    CFRelease(port);
}

輪子能上路前還有啥要做的事情

  • 數(shù)據(jù)封裝呐能,目前只是用來互相ping確認(rèn)存活提及通知去UserDefault更新數(shù)據(jù)念搬,數(shù)據(jù)傳遞能力還不夠完備易用
  • 啟動關(guān)閉Listener的線程操作封裝起來抑堡,提高安全性
  • 層級結(jié)構(gòu)理清,上層下層邏輯分離干凈
  • 晚飯還沒吃朗徊,今天還要去理發(fā)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末首妖,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子爷恳,更是在濱河造成了極大的恐慌有缆,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件温亲,死亡現(xiàn)場離奇詭異棚壁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)栈虚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門袖外,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人魂务,你說我怎么就攤上這事曼验。” “怎么了粘姜?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵鬓照,是天一觀的道長。 經(jīng)常有香客問我孤紧,道長豺裆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任号显,我火速辦了婚禮臭猜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘咙轩。我一直安慰自己获讳,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布活喊。 她就那樣靜靜地躺著,像睡著了一般量愧。 火紅的嫁衣襯著肌膚如雪钾菊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天偎肃,我揣著相機(jī)與錄音煞烫,去河邊找鬼。 笑死累颂,一個胖子當(dāng)著我的面吹牛滞详,可吹牛的內(nèi)容都是我干的凛俱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼料饥,長吁一口氣:“原來是場噩夢啊……” “哼蒲犬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起岸啡,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤原叮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后巡蘸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奋隶,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年悦荒,在試婚紗的時候發(fā)現(xiàn)自己被綠了唯欣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡搬味,死狀恐怖黍聂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情身腻,我是刑警寧澤产还,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站嘀趟,受9級特大地震影響脐区,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜她按,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一牛隅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酌泰,春花似錦媒佣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至衰琐,卻和暖如春也糊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背羡宙。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工狸剃, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狗热。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓钞馁,卻偏偏與公主長得像虑省,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子僧凰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355