iOS蓝牙开发(CoreBluetooth)

目前在iOS中蓝牙开发框架主要有以下几种

  • GameKit.framework:iOS7之前的蓝牙通讯框架,从iOS7开始过期.
  • MultipeerConnectivity.framework:iOS7开始引入的新的蓝牙通讯开发框架,用于取代GameKit。
  • CoreBluetooth.framework:功能强大的蓝牙开发框架,要求设备必须支持蓝牙4.0。
  • 前两个框架使用起来比较简单,但是缺点也比较明显:仅仅支持iOS设备,传输内容仅限于沙盒或者照片库中用户选择的文件,并且第一个框架只能在同一个应用之间进行传输(一个iOS设备安装应用A,另一个iOS设备上安装应用B是无法传输的)。CoreBluetooth就摆脱了这些束缚,它不再局限于iOS设备之间进行传输,你可以通过iOS设备向Android、Windows Phone以及其他安装有蓝牙4.0芯片的智能设备传输,因此也是目前智能家居、无线支付等热门智能设备所推崇的技术。本文主要介绍CoreBluetooth的相关开发流程.

CoreBluetooth开发模式

CoreBluetooth设计类似于客户端-服务器端的设计,peripheral和central, 可以理解成外设和中心。对应他们分别有一组相关的API和类.


CoreBluetooth架构
  • 左侧叫做中心模式,以app作为中心,连接其他的外设的,而右侧称为外设模式,使用手机作为外设别其他中心设备操作的场景。
  • 服务和特征,特征的属性(service and characteristic):
    蓝牙设备添加若干服务,服务內添加若干特征,特征就是具体键值对,提供对数据的读取和写。蓝牙中心和外设数据的交换基于服务的特征.


    外设和服务特征的关系

中心的开发

  • 流程
    <pre>
  1. 建立中心角色
  2. 扫描外设(discover)
  3. 连接外设(connect)
  4. 扫描外设中的服务和特征(discover)
    • 4.1 获取外设的services
    • 4.2 获取外设的Characteristics,获取Characteristics的值,获取 Characteristics的Descriptor和Descriptor的值
  5. 与外设做数据交互(explore and interact)
  6. 订阅Characteristic的通知
  7. 断开连接(disconnect)
    </pre>
  • 步骤
    CoreBluetooth中不管是中心还是外设的开发,均是上面的流程,通过一步步的代理回调实现的,按照流程跟着走,一步一步实现代理.
    <pre>
    //初始化
    _centralManager = [[CBCentralManager alloc] initWithDelegate:self
    queue:nil
    options:@{CBCentralManagerOptionRestoreIdentifierKey:kRestoreIdentifierKey}];
    //监听中心设备状态
    -(void)centralManagerDidUpdateState:(CBCentralManager *)central{
    if (central.state == CBCentralManagerStatePoweredOn) {
    [self writeToLogWithText:@"中心设备已打开"];
    [_centralManager scanForPeripheralsWithServices:nil options:nil];
    } //中心设备CBCentralManagerStatePoweredOn状态下就可以开始搜索外设了
    }
    -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{
    //搜索到指定的外设就可以开始连接外设了
    [_centralManager connectPeripheral:peripheral options:nil];
    }
    -(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    //连接成功后开始搜索服务
    [peripheral discoverServices:nil];
    }
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    //搜索服务成功后查找指定服务中的特征
    [peripheral discoverCharacteristics:nil forService:service];
    }
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(nonnull CBService *)service error:(nullable NSError *)error{
    //发现可用特征后,就可以进行相应的操作了,主要有以下三种
    //情景一:读取
    if (characteristic.properties & CBCharacteristicPropertyRead) {
    if ([characteristic.UUID.UUIDString isEqualToString:kReadUUID]) {
    [peripheral readValueForCharacteristic:characteristic];
    if (characteristic.value) {
    NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
    NSLog(@"读取到特征值:%@",value);
    }
    }
    }

      //情景二:通知
      if (characteristic.properties & CBCharacteristicPropertyNotify) {
          if ([characteristic.UUID.UUIDString isEqualToString:kNotifyUUID] || [characteristic.UUID.UUIDString isEqualToString:kWriteUUID]) {
              _curCharacter = characteristic;
              [peripheral setNotifyValue:YES forCharacteristic:characteristic];
              [self writeToLogWithText:@"已订阅特征通知"];
          }
      }
      
      //情景二:写数据
      if (characteristic.properties & CBCharacteristicPropertyWrite) {
          if ([characteristic.UUID.UUIDString isEqualToString:kWriteUUID]) {
              [peripheral writeValue:[@"hello,外设" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
              [self writeToLogWithText:@"写数据给外设"];
              
              _curPeripherals = peripheral;
              _curCharacter = characteristic;
          }
      }
    

}
//根据不同的场景,会调用以下三个方法
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;
}
</pre>

外设的开发

  • 流程
    <pre>
    1. 打开peripheralManager,设置peripheralManager的委托
  1. 创建characteristics,characteristics的description 创建service,把characteristics添加到service中,再把service添加到peripheralManager中
  2. 开启广播advertising
  3. 对central的操作进行响应
    • 4.1 读characteristics请求
    • 4.2 写characteristics请求
    • 4.4 订阅和取消订阅characteristics
      </pre>
  • 步骤
    <pre>
    //初始化
    _peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
    //启动外设
    -(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
    switch (peripheral.state) {
    case CBPeripheralManagerStatePoweredOn:
    //CBPeripheralManagerStatePoweredOn下添加服务和特征
    [self setupService];
    }
    //创建服务,特征并添加服务到外围设备
    -(void)setupService{

    //可通知的特征
    // CBUUID *characteristicUUID = [CBUUID UUIDWithString:kNotifyUUID];
    // _characteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];

    //可读写的特征
    CBUUID *UUID2 = [CBUUID UUIDWithString:kWriteUUID];
    _characteristic = [[CBMutableCharacteristic alloc] initWithType:UUID2 properties:CBCharacteristicPropertyWrite|CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsWriteEncryptionRequired];
    //
    //只读的特征
    // CBUUID *UUID3 = [CBUUID UUIDWithString:kReadUUID];
    // NSData *characteristicValue = [@"aaron才" dataUsingEncoding:NSUTF8StringEncoding];
    // _characteristic = [[CBMutableCharacteristic alloc] initWithType:UUID3 properties:CBCharacteristicPropertyRead value:characteristicValue permissions:CBAttributePermissionsReadable];

    CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
    _service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
    [_service setCharacteristics:@[_characteristic]];

    [_peripheralManager addService:_service];
    }
    //添加服务后开始广播,等待中心设备的连接
    -(void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(nullable NSError *)error{
    NSDictionary *dict = @{CBAdvertisementDataLocalNameKey:kPeripheralName};
    [_peripheralManager startAdvertising:dict];
    [self writeToLogWithText:@"向外围设备添加了服务"];
    }
    根据中心设备的响应,外设在代理中收到中心发来的信息,
    //订阅特征
    -(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic;
    //取消订阅
    -(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic;
    //中心设备读外设数据
    -(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request;
    //收到中心写来的数据
    -(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests;
    </pre>

UIBackgroundModes下的蓝牙数据传输

CoreBluetooth提供了非常友好的Background支持.按一下步骤,经过测试,app进入后台后,也能收到外设特征更新的值,据WWDC2013视频介绍,就算因为内存紧张,app在后台被杀掉,系统也会自动帮我们重新启动app进行蓝牙数据传输,不过这个就没测试到,不知道有没有人做过这方便的研究.

  • 设置info.plist
    

<pre>
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>bluetooth-peripheral</string>
</array>
</pre>

  • 初始化方式中添加``options:@{CBCentralManagerOptionRestoreIdentifierKey:kRestoreIdentifierKey}``
    

<pre>
_centralManager = [[CBCentralManager alloc] initWithDelegate:self
queue:nil
options:@{CBCentralManagerOptionRestoreIdentifierKey:kRestoreIdentifierKey}];
</pre>

<pre>

  • (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary<NSString *,id> *)dict{

// NSArray *scanServices = dict[CBCentralManagerRestoredStateScanServicesKey];
// NSArray *scanOptions = dict[CBCentralManagerRestoredStateScanOptionsKey];

NSArray *peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey];
for (CBPeripheral *peripheral in peripherals) {
    [self.peripherals addObject:peripheral];
    peripheral.delegate = self;
}

}
</pre>

<pre>

  • (void)centralManagerDidUpdateState:(CBCentralManager *)central{

    if (central.state == CBCentralManagerStatePoweredOn) {
    [self writeToLogWithText:@"中心设备已打开"];
    [_centralManager scanForPeripheralsWithServices:nil options:nil];

      //03,检查是否restore connected peripherals
      for (CBPeripheral *peripheral in _peripherals) {
          if (peripheral.state == CBPeripheralStateConnected) {
              NSUInteger serviceIdx = [peripheral.services indexOfObjectPassingTest:^BOOL(CBService * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                  return [obj.UUID isEqual:kServiceUUID];
              }];
              
              if (serviceIdx == NSNotFound) {
                  [peripheral discoverServices:@[kServiceUUID]];
                  continue;
              }
              
              CBService *service = peripheral.services[serviceIdx];
              NSUInteger charIdx = [service.characteristics indexOfObjectPassingTest:^BOOL(CBCharacteristic * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                  return [obj.UUID isEqual:kNotifyUUID];
              }];
              
              if (charIdx == NSNotFound) {
                  [peripheral discoverCharacteristics:@[kNotifyUUID] forService:service];
                  continue;
              }
              
              CBCharacteristic *characteristic = service.characteristics[charIdx];
              if (!characteristic.isNotifying) {
                  [peripheral setNotifyValue:YES forCharacteristic:characteristic];
              }
          }
      }
    

    }else{
    [_peripherals removeAllObjects];
    }
    }
    </pre>

<pre>

  • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    NSArray *peripheralManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothPeripheralsKey];
    NSArray *centraManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];

    for (NSString *identifier in centraManagerIdentifiers) {
    if ([identifier isEqualToString:kRestoreIdentifierKey]) {

      }
    

    }

    return YES;
    }
    </pre>

Demo

不动手写代码的学框架都是耍流氓,为了更好的学习,我也是自己都敲了一遍代码,前文中的三种场景以及后台模式都实现了,可以实现中心设备和外设之前的订阅特征,读写数据等功能.希望大家都交流.下面是传送门以及界面展示😊
github传送门 CoreBluetoothDemo

主界面

![中心设备]IMG_3660.PNG](http://upload-images.jianshu.io/upload_images/1093584-e66b42d6c1c7133f.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

外设
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 这里我们具体说明一下中心模式的应用场景。主设备(手机去扫描连接外设,发现外设服务和属性,操作服务和属性的应用。一般...
    丶逝水流年阅读 2,198评论 3 4
  • 概念 iOS开发中,谈到蓝牙现在基本最常使用的框架就是CoreBluetooth框架了,使用该框架可以iOS设备与...
    软件iOS开发阅读 1,120评论 1 4
  • 本文主要以蓝牙4.0做介绍,因为现在iOS能用的蓝牙也就是只仅仅4.0的设备 用的库就是core bluetoot...
    暮雨飞烟阅读 810评论 0 2
  • 首先进一则广告: 蓝牙技术联盟(Bluetooth SIG)2010年7月7日宣布,正式采纳蓝牙4.0核心规范(B...
    L泽阅读 1,412评论 3 4
  • 深夜里的我变一条大鱼,在深海蓝做噩梦的孤独的鲸。 我在夜里醒来,因为噩梦的缘故。梦见家里边去世了好多人,梦见妹妹不...
    橘汁儿阅读 659评论 0 1