MENU

iOS蓝牙通讯详解

December 3, 2016 • Read: 26018 • iOS

前言

最近实验室做了一个IOS设备之间使用蓝牙进行数据交互的项目。中间遇到了很多坑,现在大致讲解一下蓝牙通讯的流程。干货请直接下翻到第四节。

iOS蓝牙基础知识

背景

  • iOS的蓝牙不能用来传输文件。
  • iOS与iOS设备之间进行数据通信,使用gameKit.framework
  • iOS与其他非iOS设备进行数据通信,使用coreBluetooth.framework

iOS中蓝牙的实现方案

iOS中提供了4个框架用于实现蓝牙连接:

  • GameKit.framework
用法简单只能用于iOS设备之间的连接,多用于游戏(比如五子棋对战),从iOS7开始过期
  • MultipeerConnectivity.framework
只能用于iOS设备之间的连接,从iOS7开始引入,主要用于文件共享(仅限于沙盒的文件)
  • ExternalAccessory.framework
可用于第三方蓝牙设备交互,但是蓝牙设备必须经过苹果MFi认证(国内较少)
  • CoreBluetooth.framework(时下热门)
可用于第三方蓝牙设备交互,必须要支持蓝牙4.0
硬件至少是4s,系统至少是iOS6
蓝牙4.0以低功耗著称,一般也叫BLE(BluetoothLowEnergy)
目前应用比较多的案例:运动手坏、嵌入式设备、智能家居

CoreBluetooth

什么是CoreBluetooth

The Core Bluetooth framework lets your iOS and Mac apps communicate with Bluetooth low energy devices. For example, your app can discover, explore, and interact with low energy peripheral devices, such as heart rate monitors, digital thermostats, and even other iOS devices.

The framework is an abstraction of the Bluetooth 4.0 specification for use with low energy devices. That said, it hides many of the low-level details of the specification from you, the developer, making it much easier for you to develop apps that interact with Bluetooth low energy devices. Because the framework is based on the specification, some concepts and terminology from the specification have been adopted. This chapter introduces you to the key terms and concepts that you need to know to begin developing great apps using the Core Bluetooth framework.

CoreBluetooth框架就是苹果公司为我们提供的一个库,我们可以使用这个库和其他支持蓝牙4.0的设备进行数据交互。值得注意的是在IOS10之后的APP中,我们需要在 info.plist文件中添加NSBluetoothPeripheralUsageDescription字段否则APP会崩溃

蓝牙通讯中的外围设备和中心设备

通常,设备之间进行通讯的时候都少不了中心设备和外围设备。一般来说,外围设备具有一些需要传递给中心设备的数据,而中心设备获取这些数据之后,可以进行相应的数据处理。同时,中心设备处理完数据之后,也可以向外围设备发送信息。例如,下图1-1中外围设备(心率感应器)将数据发送给IOS或者MAC上的APP中,即中心设备。
1-1 中心设备和外围设备

中心设备可以发现并连接到发送了广播的外围设备

外围设备向中心设备传递数据的主要方式是广播。广播中携带的数据有限(大致为31字节),里面包含了外围设备所想要向中心设备提供的数据。例如下图1-2中,一个温度监控仪可能会向外广播现在房间的温度,中心设备就能拿到温度数据进行相应的一些操作。所以在BLE技术中,广播是外围设备让其他设备能感知到它的主要方式
1-2 外围设备广播

外围设备中的数据结构

外围设备中包含一个或者多个服务或提供关于连接信号强度的一些信息。服务是一个数据集合,它包含完成一个(或多个)设备某些功能或特性的相关行为。例如,一个心率感应器的服务可能携带从心率传感器发送而来的数据。服务本身是由特征或服务(即引用其它服务)组成。特征则为外围设备的服务提供了更详细的细节。例如,刚刚提到的心率服务中的特性就可能包含心率设备的位置和心率测量数据。图1-3展示了这个心率感应设备中的结构。
1-3 外围设备中的服务和特征%

中心设备和外围设备进行数据交互

  • 当中心设备成功连接到一个外围设备之后,它可以知道外围设备提供的所以服务和特征(广播的数据可能只包含一部分可用的服务)。
  • 中心设备可以对外围设备的服务特征进行读写操作从而实现和外围设备的数据交互。例如,一个APP可能会通过温度感应器获取房间当前温度,并且通过给温度感应器传递数据(像操作空调一样)改变房间温度。

中心设备、外围设备、外围设备数据的表现形式

本地中心设备对象

蓝牙数据交互方式主要都通过Core Bluetooth框架进行。

  • 当你使用中心端和 远程外围设备 进行交互的时候,你的蓝牙交互操作基本都在本地中心端执行。除非你需要设置一个远程外围设备并且通过它对中心设备作出响应,否则你的大部分蓝牙交互操作都在中心端进行。
  • 在本地中心端,一个名叫CBCentralManager的对象起着举足轻重的作用。这个类被用来管理发现或者已经连接的远程外围设备(通常这些外围设备都以CBPeripheral对象为存在方式,某种程度上可以说CBPeripheral就是外围设备,CBCentralManager就是中心设备),包括扫描、发现并且连接到广播的外围设备。图1-4展示了本地中心设备和远程外围设备在Core Bluetooth中的表现形式。
    1-4 本地中心设备在Core Bluetooth中的表现形式
  • 远程外围设备的数据由CBServiceCBCharacteristic对象表示

当你在和远程外围设备进行数据交互的时候,你实际上是在和其服务和特征进行交互。在Core Bluetooth框架中,服务由CBService对象代表。同样的,特征也由CBCharacteristic对象代表。图1-5阐述了外围设备中的结构信息。

1-5 外围设备的树状结构

本地外围设备对象

在iOS6和OS X v10.9之后,Mac和IOS设备都能够作为 本地外围设备 给包括Mac、iPhone、和iPad的设备分享数据。当你实现本地外围设备角色功能,在蓝牙交互角色中,你就表现为外围设备。

  • 在远程外围设备端,本地外围设备由CBPeripheralManager对象作为代表。这些对象被用来管理发布外围设备服务和特征数据并实现对中心设备的广播。它也用来回应中心设备的读写操作。图1-6展示了外围设备在蓝牙交互中的角色信息。
    1-6 外围设备在Core Bluetooth中的表现形式
  • 本地外围设备由CBMutableServiceCBMutableCharacteristic对象代表

当你和本地外围设备进行数据处理的时候,你其实是在和服务和特征的可变版本打交道。在Core Bluetooth框架中,本地外围设备的服务由CBMutableService代表。同样,本地外围设备特种由CBMutableCharacteristic代表。图1-7传输了本地外围设备服务和特征的树状结构。

1-7 本地外围设备服务和特征的树状结构

蓝牙通讯应用实例

对蓝牙基础知识有了一个大致了解之后,我们现在利用CoreBluetooth框架进行实战。
我们需要外围设备和中心设备模式,所以创建两个分别名为BluetoothCentralTestBluetoothPeripheralTest的项目。 在后期我们需要测试蓝牙的时候,如果只有一个IOS设备,我们可以下载一个名为 LightBlue 的软件用来模拟另一个蓝牙设备

创建外围设备

  • 外围设备的工作流程为:

1、开启外围设备管理
2、设置服务、特征、描述等信息

3、开始广播

  • 外围设备和中心设备的数据交互可以用通知的形式来进行或者通过- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests这两个方法来进行。 其中通知主要是中心设备订阅外围设备,一般用作外围设备单方面向中心设备发送数据
  • 我创建外围设备是以单例的方式来创建,代码如下,注意:在.h文件中需要声明协议CBPeripheralManagerDelegate
//
//  LARPeripheralManager.m
//  BlueToothPeripheralTest
//
//  Created by 柳钰柯 on 2016/12/4.
//  Copyright © 2016年 柳钰柯. All rights reserved.
//

#import "LARPeripheralManager.h"

static NSString *const ServiceUUID1 =  @"FFF0";
static NSString *const notiyCharacteristicUUID =  @"FFF1";
static NSString *const readwriteCharacteristicUUID =  @"FFF2";
static NSString *const ServiceUUID2 =  @"FFE0";
static NSString *const readCharacteristicUUID =  @"FFE1";
static NSString * const LocalNameKey =  @"iPhone";

@implementation LARPeripheralManager{
    // 外围设备管理器
    CBPeripheralManager *peripheralManager;
    // 定时器
    NSTimer *timer;
}

+ (instancetype)shareInstance
{
    static LARPeripheralManager *peripheral;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        peripheral = [[self alloc] init];
    });
    return peripheral;
}

- (instancetype)init
{
    if (self = [super init]) {
        NSLog(@"外围设备单例创建");
        peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:nil];
    }
    return self;
}

// 初始化一些UUID和特征信息,此处设置的一些信息,在中心设备中可以根据需要进行过滤
- (void)setUp
{
    // characteristic字段描述
    CBUUID *CBUUIDCharacteristicUserDescriptionStringUUID = [CBUUID UUIDWithString:CBUUIDCharacteristicUserDescriptionString];

    /*
     可以通知的Characteristic
     properties:CBCharacteristicPropertyNotify
     permissions: CBAttributePermissionsReadable
     */
    CBMutableCharacteristic *notiyCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:notiyCharacteristicUUID] properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];

    /*
     可读写的characteristics
     properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead
     permissions CBAttributePermissionsReadable | CBAttributePermissionsWriteable
     */
    CBMutableCharacteristic *readwriteCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:readwriteCharacteristicUUID] properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable | CBAttributePermissionsWriteable];

    //设置descriptor
    CBMutableDescriptor *readwriteCharacteristicDescription1 = [[CBMutableDescriptor alloc]initWithType: CBUUIDCharacteristicUserDescriptionStringUUID value:@"name"];


    [readwriteCharacteristic setDescriptors:@[readwriteCharacteristicDescription1]];


    /*
     只读的Characteristic
     properties:CBCharacteristicPropertyRead
     permissions CBAttributePermissionsReadable
     */
    CBMutableCharacteristic *readCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:readCharacteristicUUID] properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];


    //service1初始化并加入两个characteristics
    CBMutableService *service1 = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID1] primary:YES];

    [service1 setCharacteristics:@[notiyCharacteristic,readwriteCharacteristic]];

    //service2初始化并加入一个characteristics
    CBMutableService *service2 = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID2] primary:YES];
    [service2 setCharacteristics:@[readCharacteristic]];

    //添加后就会调用代理的- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error
    [peripheralManager addService:service1];
    [peripheralManager addService:service2];
}

#pragma mark - <CBPeripheralManagerDelegate>
// 检测蓝牙状态变化,当蓝牙状态改变时,自动回调
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
{
    switch (peripheral.state) {
        //在这里判断蓝牙设别的状态  当开启了则可调用  setUp方法(自定义)
        case CBManagerStatePoweredOn:
            NSLog(@"powered on");
            // 运行初始化方法
            [self setUp];
            break;
        case CBManagerStatePoweredOff:
            NSLog(@"powered off");
            break;

        default:
            break;
    }
}

// 添加了服务,添加服务后需要广播,一旦广播,外围设备就可以被中心设备发现,同样外围设备所携带的数据也能被中心设备捕获
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error
{
    if (error) {
        NSLog(@"%@",[error localizedDescription]);
        return;
    }
    // 添加服务后,发送广播
    // 发送广播后会自动调用
    // - (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error
    [peripheralManager startAdvertising:@{
                                          CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:ServiceUUID1],[CBUUID UUIDWithString:ServiceUUID2]],
                                          CBAdvertisementDataLocalNameKey : LocalNameKey
                                          }];
}

// 通知发送了广播
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error
{
    NSLog(@"已经开始广播");
}

// 中心设备订阅特征后会调用这个方法
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
{
    NSLog(@"订阅了%@的数据",characteristic.UUID);
    // 分配定时任务
    timer = [NSTimer scheduledTimerWithTimeInterval:1
                                             target:self
                                           selector:@selector(sendData:)
                                           userInfo:characteristic
                                            repeats:YES];
}

//中心设备取消订阅特征后调用这个方法
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{
    NSLog(@"取消订阅 %@的数据",characteristic.UUID);
    // 取消定时器
    [timer invalidate];
}

// 发送数据
- (void)sendData:(NSTimer *)t{
    CBMutableCharacteristic *characteristic = t.userInfo;
    if ([peripheralManager updateValue:[[NSString stringWithFormat:@"发送数据中"] dataUsingEncoding:NSUTF8StringEncoding]forCharacteristic:characteristic onSubscribedCentrals:nil]) {
        NSLog(@"发送数据成功");
    }else{
        NSLog(@"发送数据错误");
    }
}


// 中心设备读characteristics请求
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{
    NSLog(@"didReceiveReadRequest");
    //判断是否有读数据的权限
    if (request.characteristic.properties & CBCharacteristicPropertyRead) {
        //        NSData *data = request.characteristic.value;
        NSData *data = [[NSString stringWithFormat:@"通过characteristics读请求"] dataUsingEncoding:NSUTF8StringEncoding];
        [request setValue:data];
        //对请求作出成功响应
        [peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
    }else{
        [peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
    }
}


// 外围设备写characteristics请求
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests{
    NSLog(@"didReceiveWriteRequests");
    CBATTRequest *request = requests[0];

    //判断是否有写数据的权限
    if (request.characteristic.properties & CBCharacteristicPropertyWrite) {
        //需要转换成CBMutableCharacteristic对象才能进行写值
        CBMutableCharacteristic *c =(CBMutableCharacteristic *)request.characteristic;
        c.value = request.value;
        NSLog(@"收到中心设备发送信息:%@",[[NSString alloc] initWithData:c.value encoding:NSUTF8StringEncoding]);
        [peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
    }else{
        [peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
    }


}

// 外围设备更新描述后调用
- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral{
    NSLog(@"peripheralManagerIsReadyToUpdateSubscribers");
}
@end
  • 我们尝试用lighblue来链接一下外围设备试试,结果如图
    外围设备运行结果

创建中心设备

  • 中心设备的工作流程为:

1、创建中心设备
2、发现外围设备
3、链接外围设备
4、发现服务和特征

 4.1 获取服务
 4.2 获取特征值
 4.3 获取特征描述

5、 数据交互
6、 订阅通知
7、 断开链接

  • 中心设备中读取外围设备的数据可以使用订阅外围设备通知或者是调用[peripheral readValueForCharacteristic:<#(nonnull CBCharacteristic *)#>][peripheral readValueForDescriptor:<#(nonnull CBDescriptor *)#>]方法来获取特征或者描述的数据,至于需要获取那一层的,则需要自己定义。
  • 我是在特征值这一层通过订阅外围设备通知的方式获取数据,所以需要在- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error扫描到匹配的特征后,进行数据订阅。
  • 值得注意的是,中心设备扫描到外围设备后,想要保持连接,需要将外围设备保留下来。可以使用一个强引用变量引用,也可以将外围设备添加到数组。推荐使用后者
  • 同样,也需要遵守CBCentralManagerDelegateCBPeripheralDelegate协议
//
//  LARCentralBlueTooth.m
//  Unity-iPhone
//
//  Created by 柳钰柯 on 2016/11/18.
//
//

#import "LARCentralBlueTooth.h"

static NSString *const ServiceUUID1 =  @"FFF0";
static NSString *const notiyCharacteristicUUID =  @"FFF1";
static NSString *const readwriteCharacteristicUUID =  @"FFF2";

@interface LARCentralBlueTooth ()<CBCentralManagerDelegate,CBPeripheralDelegate>
/** 系统蓝牙管理对象 */
@property (strong, nonatomic, readwrite) CBCentralManager *manager;
/** 扫描到的设备 */
@property (strong, nonatomic, readwrite) NSMutableArray *discoverPeripheral;
/** 当前连接设备 */
@property (strong, nonatomic) CBPeripheral *currentPeripheral;
@end

@implementation LARCentralBlueTooth

+ (instancetype)shareInstance{
    static LARCentralBlueTooth *blueTooth;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        blueTooth = [[self alloc] init];
    });
    return blueTooth;
}

- (instancetype)init
{
    if(self = [super init]){
        NSLog(@"单例实例化");
        _manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_global_queue(0, 0)];
        _discoverPeripheral = [[NSMutableArray alloc] init];
    }
    return self;
}


#pragma mark - <CBCentralManagerDelegate>
//检查设备蓝牙开关的状态
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    if (central.state == CBManagerStatePoweredOn) {
        NSLog(@"蓝牙已打开");
        // 开始扫描设备
        // 扫描到设备之后会进入
        //-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI 方法
        [_manager scanForPeripheralsWithServices:nil options:nil];
    }else {
        NSLog(@"蓝牙已关闭");
    }
}

// 发现设备后,根据过滤设置进行连接
-(void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
    advertisementData:(NSDictionary<NSString *,id> *)advertisementData
                 RSSI:(NSNumber *)RSSI
{
    NSLog(@"搜索到了设备%@",peripheral.name);
    // 我的另外一个设备的名字以闫开头,可以在这里自行设置过滤
    if ([peripheral.name hasPrefix:@"闫"]) {
        // 持有设备
        if (![self.discoverPeripheral containsObject:peripheral]) {

            // 使用数组保留外围设备引用
            // [self.discoverPeripheral addObject:peripheral];

            // 使用一个strong变量强引用持有外围设备
            self.currentPeripheral = peripheral;

            // 连接设备
            [_manager connectPeripheral:peripheral options:nil];
            NSLog(@"准备连接设备%@",peripheral.name);
        }
    }
}


#pragma mark - 连接状态
// 连接设备失败自动调用
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@">>>连接%@失败>>>错误:%@",peripheral.name,[error localizedDescription]);
}

// 断开连接自动调用
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral
{
    NSLog(@">>>断开%@连接",peripheral.name);
}

// 连接成功自动调用
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    NSLog(@">>>连接%@成功",peripheral.name);
    // 设置外围设备代理
    [peripheral setDelegate:self];
    // 开始扫描外围设备的服务
    // 扫描到服务会进入
    // - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
    [peripheral discoverServices:nil];
}

#pragma mark - <CBCentralManagerDelegate>
// 扫描到设备的服务后会进入这个方法
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    if(error){
        NSLog(@"扫描服务错误:%@",[error localizedDescription]);
    }else {
        for (CBService *server in peripheral.services) {
            // 扫描服务的特征
            // 扫描到后会进入
            // - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
            [peripheral discoverCharacteristics:nil forService:server];
            NSLog(@"搜索到了一个服务:%@",server.UUID.UUIDString);
        }
    }
}

// 扫描到服务的特征值
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    if (error) {
        NSLog(@"扫描服务:%@的特征值错误:%@",service.UUID,[error localizedDescription]);
        return;
    }
    //获取到特征值
    for (CBCharacteristic *characteristic in service.characteristics){
        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:notiyCharacteristicUUID]]) {
            // 订阅特征值的数据
            [self notifyCharacteristic:peripheral characteristic:characteristic];
            // 读取发送的数据
            [peripheral readValueForCharacteristic:characteristic];
        }

    }
}

//当获取到外围设备更新的描述信息后(即数据)会调用此方法
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    if (error)
    {
        NSLog(@"更新特征值数据: %@ 错误: %@", characteristic.UUID,[error localizedDescription]);
    }
    // 这里对收到的数据进行处理,需要和发送端一致
    if (characteristic.value != nil) {
        NSString *data = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
        NSLog(@"收到外围设备发送的信息:%@",data);
        [peripheral writeValue:[[NSString stringWithFormat:@"get"] dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
        }

}

// 设置通知
- (void)notifyCharacteristic:(CBPeripheral *)peripheral
              characteristic:(CBCharacteristic *)characteristic{
    NSLog(@"订阅通知成功");
    //设置通知
    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
}

// 取消通知
- (void)cancelNotifyCharacteristic:(CBPeripheral *)peripheral
                    characteristic:(CBCharacteristic *)characteristic{

    [peripheral setNotifyValue:NO forCharacteristic:characteristic];
}

//停止扫描并断开连接
-(void)disconnectPeripheral:(CBCentralManager *)centralManager
                 peripheral:(CBPeripheral *)peripheral{
    //停止扫描
    [centralManager stopScan];
    //断开连接
    [centralManager cancelPeripheralConnection:peripheral];
}

@end
  • 测试结果如下图所示:
    中心设备获取数据

结语

  • 写了几个小时终于写完了,觉得不错的话,请点个赞~
  • 这个也是我自己琢磨、学习了几天之后学会的,难免有错误,请发现错误后联系我,我会及时纠正。谢谢!
  • 如果有疑问,欢迎留言或者给我发邮件。
  • 项目地址: 蓝牙Demo

参考

Core Bluetooth Overview
iOS蓝牙开发(一)蓝牙相关基础知识

Archives QR Code
QR Code for this page
Tipping QR Code