藍牙通訊基於 client-server 構架,由兩個角色進行通訊,分別是 Central
和 Peripheral
,中文環境能夠理解爲主設備
和從設備
。swift
Peripheral
角色主要做爲數據的提供方,好比 Apple Watch;Central
主要做爲數據需求方,一般爲 iPhone/iPad 等產品。緩存
Peripheral
提供一系列的服務(service),每種服務含有一個或多個特徵屬性(characteristics)。bash
iOS 提供 CoreBuletooth 框架,做爲藍牙 4.0 通訊的基礎。對應每個 Peripheral
,CoreBuletooth 框架會爲其分配一個 UUID,據測試,通常狀況下,這個 UUID 在一個 iOS 設備上是不會改變的,但對於不一樣的 iOS 設備,拿到的 UUID 卻又不一樣。Central
的 ID 由底層框架維護,上層並不知曉。app
在通訊時,一方向外界廣播數據,並攜帶接收方的 ID,周邊全部藍牙設備都會收到消息,若是發現本身與該 ID 匹配,則處理消息,否者忽略。框架
在創建鏈接前,須要知道周邊設備的 UUID,須要一個發現的過程。異步
未鏈接的藍牙設備,會不斷的向周邊廣播數據。數據中攜帶它具備的服務(Service),名稱,信號強度,廠商信息等。在 iOS 中以 CBPeripheral
類表示。ide
中心設備經過發現的方式來找到周邊設備進而創建鏈接通訊。性能
CBCentralManager 類表明 iOS 設備,開始使用前,先進行初始化操做。測試
let centralManager = CBCentralManager()
centralManager.delegate = self
複製代碼
CBCentralManager
初始或會驅動硬件,這個過程是異步的,若是驅動成功/失敗,會經過代理方法告知:ui
func centralManagerDidUpdateState(_ central: CBCentralManager)
複製代碼
提示:若是代理方法沒有調用,控制檯輸出
[CoreBluetooth] XPC connection invalid
,極可能是centralManager
實例被釋放了。詳細參考XPC Error CoreBluetooth | Apple Developer Forums
Central
進入 powerOn
狀態後,纔可執行掃描:
centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
複製代碼
若是清楚本身所需的藍牙設備服務,withServices 參數傳入具體的服務 UUID 列表,若是傳 nil
默認發現全部設備。options
用於指定發現的規則,比較經常使用的是 CBCentralManagerScanOptionAllowDuplicatesKey
,默認狀況下,中心設備每收到一個包,就是調用一次 Delegate 方法,設置爲 false
,會每隔一段時間接收一次周邊設備的廣播信號,若是隻是爲了發現鏈接設備,這樣設置能夠節省電量以及提升 App 的性能。
centralManager
發現的設備的回調:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// peripheral:發現的設備
// advertisementData: 廣播數據字典
// RSSI: 信號強度
}
複製代碼
廣播數據包含的內容在Advertisement Data Retrieval Keys | Apple Developer Documentation。須要注意的是,這裏含有的名稱字段和peripheral.name
的值並不必定相同。後者是前者的系統級緩存,而且沒有給出緩存失效時間,若是藍牙設備中途修改過名稱,讀到的極可能是失效的緩存,真實的名稱在 CBAdvertisementDataLocalNameKey
中。雖然修更名稱的可能性很小,可是不免也會有人遇到這個坑。
須要注意的是,發現的設備必須主動去持有,否者在超出做用域就釋放了。
發現目標設備後,鏈接設備。
central.connect(peripheral, options: nil)
複製代碼
若是設備鏈接成功,回調代理方法:
optional public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)
複製代碼
若是鏈接失敗,回調:
optional public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?)
複製代碼
該方法也會在設備超出鏈接返回的時候調用。
在 iOS 中,存在系統級的藍牙鏈接方式,叫作配對。配對的設備可在設置->藍牙->個人設備
查看。配對的設備,即便 kill 應用程序,底層仍會持有鏈接的狀態,CBCentralManager
提供獲取已配對設備的方法。
open func retrieveConnectedPeripherals(withServices serviceUUIDs: [CBUUID]) -> [CBPeripheral]
複製代碼
不過,須要提供獲取已鏈接設備的 serviceUUIDs
,不管是哪一個應用程序配對的藍牙設備只要具備對應的服務都會被獲取。
最後,獲取到的設備只是底層鏈接,上層應用仍然須要調用 connectPeripheral:options:
方法,等待centralManager:didConnectPeripheral:
回調,使其進入鏈接狀態。
創建過鏈接的設備,系統都會緩存它的信息,只要它的硬件惟一標識沒有修改,系統爲其分配的 identifier
會始終不變。因此,創建過鏈接的設備,可經過保存它的 identifier
來再次獲取,並創建鏈接。
open func retrievePeripherals(withIdentifiers identifiers: [UUID]) -> [CBPeripheral]
複製代碼
創建鏈接後,須要發現該藍牙設備所具備的服務,每一個服務有對應的 UUID。
peripheral.discoverServices(nil)
複製代碼
通常藍牙設備具備多種服務, 若是但願發現全部的服務,傳 nil
。在開發中,咱們清楚所須要的服務類型,最好傳入具體的服務 UUID。成功發現服務會回調 CBPeripheralDelegate
的以下方法:
optional public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?)
複製代碼
服務類型保存在 services
屬性中。
發現目標服務後,下一步是該發現服務中的具體特徵(Characteristics),由於數據的讀寫對象是它。
peripheral.discoverCharacteristics(nil, for: service)
複製代碼
一樣特徵也具備 UUID,若是傳 nil
將獲取到該服務下的全部特徵,固然若是知道目標特徵的 UUID,傳對應的 ID 可以提升效率。特徵發現成功後,會有以下回調:
optional public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
複製代碼
目標特徵保存在 service.characteristics
中,接着能夠對它們進行讀寫操做。
單一的數據存儲在 Service 的 Characteristic 中,好比說溫度。
有兩種方式讀取 Characteristic 中的數據:
open func readValue(for characteristic: CBCharacteristic)
複製代碼
若是讀取到數據,回調代理方法:
optional public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)
複製代碼
數據存儲於 charateristic.value
中。
並非全部的特徵都是可讀的,需提早檢查檢查 CBCharacteristic
的properties
屬性。確保它包含 CBCharacteristicPropertyRead
,若是不包含,上述代理方法會返回錯誤。
有些狀況下,特徵值會動態的變化,若是都須要手動去獲取效率會比較低。Characteristic 還有一個被訂閱的功能,訂閱以後一旦特徵值發生變化就會經過回調方法通知 App。
peripheral.setNotifyValue(true, for: characteristic)
複製代碼
訂閱成功與否的狀態一樣經過代理方法返回:
optional public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?)
複製代碼
固然並非全部的 Characteristic 都有被訂閱的功能。一樣須要檢查properties
屬性,包含 Notify
或者 Indicate
才能被訂閱。
有時候還須要向 Peripheral 發送數據,其實是對它的某個特徵值進行寫操做,寫操做有兩種形式,有回覆和沒有回覆。
peripheral.peripheral.writeValue(data, for: characteristic, type: .withResponse)
複製代碼
.withResponse
表示有回覆的類型,回覆以代理的形式通知:
optional public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?)
複製代碼
若是寫入操做失敗,錯誤信息保存在 error
中。
.withoutResponse
不會有回覆,只會盡最大努力的寫,但沒法保證能寫成功,若是寫失敗,不會有任何通知和錯誤信息。
傳入的 data 數據,在內部會被拷貝,後續修改或者釋放影響寫操做。
一樣,並非全部的特徵都具備寫的功能,在寫以前需檢查該特徵的 properties
屬性是否包含 write
或者 writeWithoutResponse
。