iOS ExternalAccessory框架初探

2017年8月9日更新:

關(guān)于調(diào)式废累,debug倚搬,Xcode 9 可以進(jìn)行無線調(diào)試了冶共,當(dāng)你的硬件占用了Lightning口,手機(jī)也可以無線連接Xcode調(diào)試每界,具體操作不做贅述捅僵,可參考Xcode9下iOS11適配注意事項(xiàng)及無線部署調(diào)試


這個(gè)框架能做什么

顧名思義:External:外部的;Accessory:配件眨层。應(yīng)該是和外部設(shè)備相關(guān)的一個(gè)框架庙楚。

ExternalAccessory框架,就是可以用來和Lightning接口的硬件趴樱,或者藍(lán)牙(2.1)設(shè)備進(jìn)行連接馒闷、通訊的這么一個(gè)框架。(當(dāng)然叁征,也可以和30-pin接口的硬件連接纳账、通訊——不過現(xiàn)在幾乎沒有這種接口的設(shè)備了吧~)

就是你現(xiàn)在有一個(gè)Lightning耳機(jī)(iPhone7, 7Plus的耳機(jī)~),或者有一個(gè)藍(lán)牙2.1的音箱航揉,你要寫一個(gè)App去控制這些設(shè)備塞祈,你要選用的框架金刁,就是ExternalAccessory帅涂。

比如我前公司,幫美國(guó)公司代工的一款藍(lán)牙2.1的音箱尤蛮,寫了一個(gè)App進(jìn)行控制(燈光媳友、音效)骤视;還有現(xiàn)在公司滨彻,做Lightning設(shè)備的App缆毁,用來對(duì)耳機(jī)進(jìn)行簡(jiǎn)單的控制肺蔚、固件升級(jí)逗余。這都需要用到ExternalAccessory框架。

框架簡(jiǎn)介

ExternalAccessory框架的主要功能坟岔,就是提供一個(gè)管道顶瞳,讓外圍設(shè)備可以和基于iOS系統(tǒng)的設(shè)備進(jìn)行通訊。

主要的幾個(gè)類:

  • EAAccessory:表示你連接的設(shè)備赶促。
  • EAAccessoryManager:有一個(gè)重要的屬性connectedAccessories液肌,用來獲取已經(jīng)連接上手機(jī)的設(shè)備。
  • EASession:這個(gè)類主要用來建立通道鸥滨,讓App和設(shè)備可以進(jìn)行數(shù)據(jù)的傳輸(發(fā)送和接收)

設(shè)備的連接

其實(shí)設(shè)備的連接嗦哆、斷開,都是系統(tǒng)自動(dòng)完成的婿滓。

EAAccessoryManager類中有一個(gè)屬性connectedAccessories(一個(gè)array)老速,里面就已經(jīng)包含了所有已經(jīng)連接的外圍設(shè)備(EAAccessory對(duì)象)。像什么設(shè)備名稱凸主、制造廠商橘券、硬件型號(hào)、固件型號(hào)等等信息秕铛,都可以在EAAccessory對(duì)象中拿得到约郁。

但是,ExternalAccessory框架但两,并不會(huì)自動(dòng)幫你監(jiān)控設(shè)備的斷開鬓梅、連接狀態(tài)。如果你想拿到設(shè)備連接谨湘、斷開的回調(diào)绽快,則需要手動(dòng)敲一些代碼了:

拿到連接、斷開的回調(diào)

需要注冊(cè)通告紧阔,即調(diào)用EAAccessoryManager的方法registerForLocalNotifications坊罢。

當(dāng)有硬件連接,ExternalAccessory框架就會(huì)發(fā)送EAAccessoryDidConnectNotification這個(gè)通告擅耽,當(dāng)有硬件斷開連接活孩,就會(huì)發(fā)出EAAccessoryDidDisconnectNotification通告。所以乖仇,要監(jiān)聽憾儒、接收這兩個(gè)通告。

// 注冊(cè)通告
[[EAAccessoryManager sharedAccessoryManager] registerForLocalNotifications];

// 監(jiān)聽EAAccessoryDidConnectNotification通告(有硬件連接就會(huì)回調(diào)Block)
[[NSNotificationCenter defaultCenter] addObserverForName:EAAccessoryDidConnectNotification
                                                  object:nil
                                                   queue:nil
                                              usingBlock:^(NSNotification * _Nonnull note) {
                                                  
                                                  // 從已經(jīng)連接的外設(shè)中查找我們的設(shè)備(根據(jù)協(xié)議名稱來查找)
                                                  [self searchOurAccessory];
}];

[[NSNotificationCenter defaultCenter] addObserverForName:EAAccessoryDidDisconnectNotification
                                                  object:nil
                                                   queue:nil
                                              usingBlock:^(NSNotification * _Nonnull note) {
                                                  // Do something what you want
}];

此外乃沙,硬件斷開連接起趾,除了通告回調(diào),框架還提供了Delegate的回調(diào)方式警儒,遵守EAAccessoryDelegate協(xié)議训裆,并實(shí)現(xiàn)accessoryDidDisconnect:這個(gè)可選方法(這個(gè)協(xié)議中的唯一一個(gè)方法),也可以拿到硬件斷開連接的回調(diào)。(好奇怪边琉,Apple為什么單單只弄這么一個(gè)方法属百?)

識(shí)別硬件

好了,我們知道硬件連接進(jìn)行了变姨,那怎么知道是不是我們的硬件呢诸老?

蘋果公司將這個(gè)能識(shí)別硬件身份的東東叫做「協(xié)議」。本質(zhì)上就是一個(gè)字符串钳恕,一個(gè)由反向域名組成的字符串别伏,例如om.apple.myProtocol。

而這個(gè)協(xié)議(字符串)的定義忧额,是由硬件的生產(chǎn)廠商定義的厘肮,所以App開發(fā)人員,要和廠商溝通拿到這部分的資料睦番。

所以我們要做幾件事件:

  • 0类茂、導(dǎo)入框架(這個(gè)不用說了吧~)#import <ExternalAccessory/ExternalAccessory.h>
  • 1、在Info.plist中托嚣,增加UISupportedExternalAccessoryProtocols這個(gè)key巩检,然后值賦為協(xié)議名稱(就是那個(gè)反向域名字符串)。(其實(shí)是一個(gè)array示启,所以這里可以支持多個(gè)協(xié)議兢哭,不分順序)
  • 2、在硬件已經(jīng)連接的回調(diào)中夫嗓,遍歷所有已經(jīng)連接的設(shè)備迟螺,根據(jù)協(xié)議名稱找到自己的硬件(實(shí)現(xiàn)上述代碼的searchOurAccessory方法):
// 從已經(jīng)連接的外設(shè)中查找我們的設(shè)備(根據(jù)協(xié)議名稱來查找)
- (void)searchOurAccessory {
    NSMutableString *info = [[NSMutableString alloc] init];

    // search our device
    for (EAAccessory *accessory in [EAAccessoryManager sharedAccessoryManager].connectedAccessories) {

        if ([kSPKLightingHeadphoneProtocolString isEqualToString:[accessory.protocolStrings firstObject]] == YES) {

            // 硬件的協(xié)議字符串和硬件廠商提供的一致,這個(gè)就是我們要找的設(shè)備了舍咖!
            
            // log:可以打印一下該硬件的相關(guān)資訊
            for (NSString *proStr in accessory.protocolStrings) {
                [info appendFormat:@"protocolString = %@\n", proStr];
            }
            [info appendFormat:@"\n"];
            [info appendFormat:@"manufacturer = %@\n", accessory.manufacturer];
            [info appendFormat:@"name = %@\n", accessory.name];
            [info appendFormat:@"modelNumber = %@\n", accessory.modelNumber];
            [info appendFormat:@"serialNumber = %@\n", accessory.serialNumber];
            [info appendFormat:@"firmwareRevision = %@\n", accessory.firmwareRevision];
            [info appendFormat:@"hardwareRevision = %@\n", accessory.hardwareRevision];

            // Log...
        }
    }
}

另外矩父,監(jiān)視硬件連接的通告Block回調(diào),NSNotification * _Nonnull note這個(gè)參數(shù)排霉,其實(shí)是包含了EAAccessory對(duì)象窍株,我們也可以直接通過EAAccessoryKey這個(gè)key拿到EAAccessory對(duì)象,再對(duì)比協(xié)議字符串是否相同攻柠,從而直接拿到已經(jīng)連接的硬件球订,無須遍歷connectedAccessories數(shù)組。

傳輸數(shù)據(jù)(指令)

創(chuàng)建EASession辙诞、打開輸入辙售、輸出通道

App和外圍設(shè)備通訊轻抱、數(shù)據(jù)傳輸飞涂,靠的是NSInputStream和NSOutputStream對(duì)象,而這兩個(gè)對(duì)象是EASession的兩個(gè)屬性。所以我們要?jiǎng)?chuàng)建EASession對(duì)象较店,謂曰:打開傳輸通道()士八。

  • 0、遵守NSStreamDelegate協(xié)議梁呈,類似:@interface YourClassName()<NSStreamDelegate>婚度,用于后面拿到相關(guān)回調(diào)。
  • 1官卡、創(chuàng)建EASession并打開輸入蝗茁、輸出通道,類似如下代碼:
- (BOOL)openSession {
    // 根據(jù)已經(jīng)連接的EAAccessory對(duì)象和這個(gè)協(xié)議(反向域名字符串)來創(chuàng)建EASession對(duì)象寻咒,并打開輸入哮翘、輸出通道 
    self.session = [[EASession alloc] initWithAccessory:self.accessory forProtocol: kSPKLightingHeadphoneProtocolString];
    if(self.session != nil) {
        // open input stream
        self.session.inputStream.delegate = self;
        [self.session.inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
        [self.session.inputStream open];
        
        // open output stream
        self.session.outputStream.delegate = self;
        [self.session.outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
        [self.session.outputStream open];
    }
    else {
        NSLog(@"Failed to create session");
    }
    
    return (nil != self.session);
}

到此為止,就完整創(chuàng)建了一個(gè)包含accessory對(duì)象毛秘、并已經(jīng)可以進(jìn)行數(shù)據(jù)發(fā)送和接收的EASession對(duì)象了饭寺。

stream:handleEvent:回調(diào):

不過,雖然數(shù)據(jù)傳輸通道已經(jīng)打開了叫挟,但是怎么發(fā)送艰匙、接收數(shù)據(jù)呢?或者說抹恳,怎么知道什么時(shí)候可以發(fā)送數(shù)據(jù)员凝,什么時(shí)候要接收數(shù)據(jù)?

注意我們剛剛遵守了NSStreamDelegate協(xié)議奋献,這里就是利用delegate回調(diào)來監(jiān)聽input stream和output stream的數(shù)據(jù)绊序。

// delegate回調(diào)的方法
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
    switch (eventCode) {
        case NSStreamEventNone:
            break;
        case NSStreamEventOpenCompleted:
            break;
        case NSStreamEventHasBytesAvailable:
            //NSLog(@"Input stream is ready");
            // 接收到硬件數(shù)據(jù)了,根據(jù)指令定義對(duì)數(shù)據(jù)進(jìn)行解析秽荞。
            [self readFromDevice];
            break;
        case NSStreamEventHasSpaceAvailable:
            //NSLog(@"Output stream is ready");
            // 可以發(fā)送數(shù)據(jù)給硬件了
            [self writeToDevice];
            break;
        case NSStreamEventErrorOccurred:
            break;
        case NSStreamEventEndEncountered:
            break;
        default:
            break;
    }
}

  • HasBytesAvailable:表示stream中有數(shù)據(jù)需要讀戎韫(硬件發(fā)送了數(shù)據(jù)給App)
  • HasSpaceAvailable:表示stream中可以接收數(shù)據(jù)的寫入(App發(fā)送了數(shù)據(jù)給硬件)——當(dāng)然,不是每次都需要等到這個(gè)回調(diào)執(zhí)行扬跋,App才能發(fā)送數(shù)據(jù)給硬件阶捆,你可以判斷stream的hasBytesAvailable屬性,如果為Yes钦听,照樣可以直接發(fā)送數(shù)據(jù)給硬件洒试。類似如下:
BOOL isAvailable = self.session.outputStream.hasSpaceAvailable;
if (isAvailable == YES) {
    [self writeToDevice];
}

發(fā)送數(shù)據(jù)、接收數(shù)據(jù)的具體方法:

  • 發(fā)送數(shù)據(jù):
    outputStream的write:maxLength:方法朴上,類似如下:
[self.session.outputStream write:[self.writeData bytes] maxLength:self.writeDataLen];
  • 接收數(shù)據(jù):
    inputStream的read:maxLength:方法垒棋,類似如下:
[self.session.inputStream read:buffer maxLength:SPK_INPUT_DATA_BUFFER_LEN];

到此,我們用ExternalAccessory框架痪宰,進(jìn)行了從識(shí)別硬件連接叼架、獲取硬件畔裕、打開傳輸通道、發(fā)送數(shù)據(jù)乖订、接收數(shù)據(jù)的完整過程扮饶。

調(diào)試、Debug

我們開發(fā)的是一個(gè)Lightning接口設(shè)備的App乍构,當(dāng)手機(jī)連接硬件時(shí)甜无,就沒辦法連接電腦進(jìn)行調(diào)試,當(dāng)手機(jī)連接電腦時(shí)哥遮,就沒辦法連接硬件進(jìn)行測(cè)試岂丘。所以整個(gè)開發(fā)調(diào)試、Debug無從下手眠饮。網(wǎng)站上咨詢了蘋果元潘,也在StackOverflow上提問,都沒有得到解決方案君仆。

后來我就腦洞大開翩概,把需要打印的日志收集起來,通過一個(gè)TextView返咱,顯示到App上做調(diào)試用(如下圖)钥庇。也算是一個(gè)權(quán)宜之計(jì),誰有更好的辦法么~

將Log轉(zhuǎn)移到App界面上進(jìn)行Debug

如有謬誤咖摹,敬請(qǐng)斧正评姨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市萤晴,隨后出現(xiàn)的幾起案子吐句,更是在濱河造成了極大的恐慌,老刑警劉巖店读,帶你破解...
    沈念sama閱讀 221,331評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗦枢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡屯断,警方通過查閱死者的電腦和手機(jī)文虏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來殖演,“玉大人氧秘,你說我怎么就攤上這事∨烤茫” “怎么了丸相?”我有些...
    開封第一講書人閱讀 167,755評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)彼棍。 經(jīng)常有香客問我灭忠,道長(zhǎng)膳算,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,528評(píng)論 1 296
  • 正文 為了忘掉前任更舞,我火速辦了婚禮,結(jié)果婚禮上坎吻,老公的妹妹穿的比我還像新娘缆蝉。我一直安慰自己,他們只是感情好瘦真,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,526評(píng)論 6 397
  • 文/花漫 我一把揭開白布刊头。 她就那樣靜靜地躺著,像睡著了一般诸尽。 火紅的嫁衣襯著肌膚如雪原杂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,166評(píng)論 1 308
  • 那天您机,我揣著相機(jī)與錄音穿肄,去河邊找鬼。 笑死际看,一個(gè)胖子當(dāng)著我的面吹牛咸产,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仲闽,決...
    沈念sama閱讀 40,768評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼脑溢,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了赖欣?” 一聲冷哼從身側(cè)響起屑彻,我...
    開封第一講書人閱讀 39,664評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎顶吮,沒想到半個(gè)月后社牲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,205評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悴了,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,290評(píng)論 3 340
  • 正文 我和宋清朗相戀三年膳沽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片让禀。...
    茶點(diǎn)故事閱讀 40,435評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挑社,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出巡揍,到底是詐尸還是另有隱情痛阻,我是刑警寧澤,帶...
    沈念sama閱讀 36,126評(píng)論 5 349
  • 正文 年R本政府宣布腮敌,位于F島的核電站阱当,受9級(jí)特大地震影響俏扩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弊添,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,804評(píng)論 3 333
  • 文/蒙蒙 一录淡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧油坝,春花似錦嫉戚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至瞬女,卻和暖如春窍帝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诽偷。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工坤学, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人报慕。 一個(gè)月前我還...
    沈念sama閱讀 48,818評(píng)論 3 376
  • 正文 我出身青樓拥峦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親卖子。 傳聞我的和親對(duì)象是個(gè)殘疾皇子略号,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,442評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 在iOS中框架是一個(gè)目錄,包含了共享資源庫(kù)洋闽,用于訪問該資源庫(kù)中儲(chǔ)存的代碼的頭文件玄柠,以及圖像、聲音文件等其他資源诫舅。共...
    wo不懂閱讀 1,430評(píng)論 3 3
  • 在iOS中框架是一個(gè)目錄羽利,包含了共享資源庫(kù),用于訪問該資源庫(kù)中儲(chǔ)存的代碼的頭文件刊懈,以及圖像这弧、聲音文件等其他資源。共...
    ch123閱讀 1,780評(píng)論 0 1
  • 6轉(zhuǎn)載-->>IOS框架和服務(wù) 在iOS中框架是一個(gè)目錄虚汛,包含了共享資源庫(kù)匾浪,用于訪問該資源庫(kù)中儲(chǔ)存的代碼的頭文件,...
    李小六_閱讀 3,617評(píng)論 2 24
  • __block和__weak修飾符的區(qū)別其實(shí)是挺明顯的:1.__block不管是ARC還是MRC模式下都可以使用卷哩,...
    LZM輪回閱讀 3,327評(píng)論 0 6
  • 自律的人看得到美好的未來蛋辈! 最美的年齡時(shí)間會(huì)記錄。
    九重艷陽(yáng)閱讀 188評(píng)論 0 1