iOS藍(lán)牙 CoreBluetooth

CoreBluetooth 是基于藍(lán)牙 ble 技術(shù)實(shí)現(xiàn)的,現(xiàn)在的智能手環(huán)手表都使用了低功耗藍(lán)牙技術(shù)熄诡。

CoreBluetooth 場(chǎng)景中藍(lán)牙對(duì)象分為兩種:

中心設(shè)備(CBCentralManager * cbcManager): 中心設(shè)備負(fù)責(zé)掃描接收廣播凰浮,連接外設(shè)設(shè)備袜茧,向外設(shè)設(shè)備進(jìn)行讀寫操作笛厦。
外設(shè)設(shè)備(CBPeripheral * peripheral): 向外發(fā)送廣播裳凸,向中心設(shè)備讀寫操作。
注意: <u>一個(gè)設(shè)備既可以是中心設(shè)備逗宁,又可以是外設(shè)設(shè)備疙剑,但是不能同時(shí)是中心設(shè)備和外設(shè)設(shè)備言缤,同一時(shí)間管挟,只能扮演一種角色僻孝。</u>

基礎(chǔ)知識(shí)介紹:

CBPeripheral

// -------------------- CBPeripheral 屬性 --------------------
// 外設(shè)狀態(tài)
typedef NS_ENUM(NSInteger, CBPeripheralState) {
    CBPeripheralStateDisconnected = 0,  // 沒有連接
    CBPeripheralStateConnecting,        // 正在連接
    CBPeripheralStateConnected,         // 已經(jīng)連接
    CBPeripheralStateDisconnecting NS_AVAILABLE(10_13, 9_0),
} NS_AVAILABLE(10_9, 7_0);
@interface CBPeripheral : CBPeer
@property(weak, nonatomic, nullable) id<CBPeripheralDelegate> delegate;
@property(retain, readonly, nullable) NSString *name;   // 外設(shè)名字
@property(retain, readonly, nullable) NSNumber *RSSI;   // 信號(hào)強(qiáng)度
@property(readonly) CBPeripheralState state;           // 外設(shè)狀態(tài)
@property(retain, readonly, nullable) NSArray<CBService *> *services;   // 外設(shè)包含的服務(wù)(每個(gè)服務(wù)里面可能包含多個(gè)特征)
@property(readonly) BOOL canSendWriteWithoutResponse;   // 判斷讀寫性
 
// -------------------- CBPeripheral 常用方法 --------------------
// 查找外設(shè)包含的服務(wù),serviceUUIDs 為 nil 則查找該外設(shè)包含的所有服務(wù)荞雏,執(zhí)行該方法會(huì)觸發(fā)代理方法
- (void)discoverServices:(nullable NSArray<CBUUID *> *)serviceUUIDs;
 
// 掃描特征的描述凤优,執(zhí)行該方法會(huì)觸發(fā)代理方法
- (void)discoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic;
 
// 掃描服務(wù)里包含的服務(wù)筑辨,執(zhí)行該方法會(huì)出發(fā)代理方法
- (void)discoverIncludedServices:(nullable NSArray<CBUUID *> *)includedServiceUUIDs forService:(CBService *)service;
 
// 掃描服務(wù)包含的特征棍辕,執(zhí)行該方法會(huì)出發(fā)代理方法
- (void)discoverCharacteristics:(nullable NSArray<CBUUID *> *)characteristicUUIDs forService:(CBService *)service;
 
// 讀取某特征的數(shù)據(jù) 
- (void)readValueForCharacteristic:(CBCharacteristic *)characteristic;
 
// 讀取描述的值
- (void)readValueForDescriptor:(CBDescriptor *)descriptor;(CBCharacteristicWriteType)type;
 
// 通過特征寫入數(shù)據(jù)
- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:
 
// 通過描述寫入數(shù)據(jù)
- (void)writeValue:(NSData *)data forDescriptor:(CBDescriptor *)descriptor;
 
// 設(shè)置通知
- (void)setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic;
 
// -------------------- 代理方法 --------------------
// 掃描外設(shè)服務(wù)觸發(fā)該代理痢毒,如果找打服務(wù)哪替,可以在這里調(diào)用 discoverCharacteristics 方法查找該服務(wù)的特征
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error;
 
// 掃描服務(wù)包含的服務(wù)(服務(wù)里面可能會(huì)包含服務(wù))
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(NSError *)error;
 
// 查找服務(wù)的特征凭舶,一個(gè)服務(wù)可能有多個(gè)特征帅霜,一個(gè)特征可能有多個(gè)屬性身冀,CBCharacteristic 是一個(gè)枚舉值
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(nonnull CBService *)service error:(nullable NSError *)error;
 
// 接收通知代理
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;
 
// 讀取 Characteristic 中的值搂根,調(diào)用 readValueForCharacteristic 方法會(huì)觸發(fā)該代理剩愧,需要在該代理方法里面獲取具體的數(shù)據(jù)
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;
 
// 向 CBCharacteristic 寫入數(shù)據(jù)的回調(diào)
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;

CBCentralManager

// 中心設(shè)備的狀態(tài)穴翩,創(chuàng)建中心設(shè)備的時(shí)候芒帕,會(huì)觸發(fā)代理副签,在代理里面判斷中心設(shè)備狀態(tài)基矮,在狀態(tài)為 CBManagerStatePoweredOn 的時(shí)候可以開始掃描廣播
typedef NS_ENUM(NSInteger, CBCentralManagerState) {
   CBCentralManagerStateUnknown = CBManagerStateUnknown,            // 未知
   CBCentralManagerStateResetting = CBManagerStateResetting,        // 正在重置
   CBCentralManagerStateUnsupported = CBManagerStateUnsupported,    // 不支持
   CBCentralManagerStateUnauthorized = CBManagerStateUnauthorized,  // 未認(rèn)證
   CBCentralManagerStatePoweredOff = CBManagerStatePoweredOff,      // 沒有打開藍(lán)牙
   CBCentralManagerStatePoweredOn = CBManagerStatePoweredOn,        // 藍(lán)牙以打開,可以掃描廣播
} NS_DEPRECATED(10_7, 10_13, 5_0, 10_0, "Use CBManagerState instead");
@interface CBCentralManager : CBManager
@property(nonatomic, weak, nullable) id<CBCentralManagerDelegate> delegate;
@property(nonatomic, assign, readonly) BOOL isScanning ;    // 判斷藍(lán)牙是否正在掃描廣播
// 藍(lán)牙中心設(shè)備初始化方法钢悲,需要指定 delegate 并實(shí)現(xiàn)代理方法
- (instancetype)initWithDelegate:(nullable id<CBCentralManagerDelegate>)delegate
                        queue:(nullable dispatch_queue_t)queue;
                        
// -------------------- 常用方法 --------------------
// 通過服務(wù) ID 來掃描外設(shè)
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;
 
// 連接外設(shè)
- (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options;
 
// 取消連接
- (void)cancelPeripheralConnection:(CBPeripheral *)peripheral;
 
// 停止掃描
- (void)stopScan;
 
// 通過 ID 數(shù)組查找外設(shè)
- (NSArray<CBPeripheral *> *)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID *> *)identifiers NS_AVAILABLE(10_9, 7_0);
 
// 通過 服務(wù)的 UUID 數(shù)組查找外設(shè)
- (NSArray<CBPeripheral *>*)retrieveConnectedPeripheralsWithServices:(NSArray<CBUUID *> *)serviceUUIDs NS_AVAILABLE(10_9, 7_0);
 
// @protocol CBCentralManagerDelegate <NSObject> 代理方法
 
// 中心設(shè)備開始掃描之后莺琳,每發(fā)現(xiàn)一個(gè)外設(shè)就會(huì)調(diào)用一次該方法惭等,同一設(shè)備可能會(huì)被發(fā)現(xiàn)多次办铡,掃描頻率基本固定,外設(shè)發(fā)送的廣播不一定每一個(gè)都能接收到稚补,如果使用該方法獲取廣播數(shù)據(jù)的時(shí)候需要注意
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;
 
// 中心設(shè)備連接上外設(shè)之后調(diào)用
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
 
// 連接失敗調(diào)用
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
 
// 斷開連接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
 
// 中心設(shè)備狀態(tài)改變的時(shí)候調(diào)用
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;
 
// 中心設(shè)備狀態(tài)重置的時(shí)候調(diào)用
- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary<NSString *, id> *)dict;

連接過程

外設(shè)設(shè)備向外廣播
中心設(shè)備接收廣播(廣播包含了廣播內(nèi)容和外設(shè)的名字课幕,信號(hào)強(qiáng)度等屬性(iOS 的 CoreBluetooth 框架不能從廣播里面獲取到外設(shè) MAC 地址乍惊,所以不能使用 MAC 地址來區(qū)分外設(shè))

掃描
連接
掃描外設(shè)所包含的服務(wù) Services
掃描每一個(gè)服務(wù)所包含的特征 Characteristics
判斷特征的屬性 CBCharacteristicProperties(根據(jù)需求保存對(duì)應(yīng)屬性的 Characteristics污桦,后面讀/寫的時(shí)候只能往可讀/可寫屬性的特征里面寫匙监, CBCharacteristicProperties 是一個(gè)枚舉)

typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
    CBCharacteristicPropertyBroadcast= 0x01,            // 廣播屬性
    CBCharacteristicPropertyRead= 0x02,                 // 可讀屬性
    CBCharacteristicPropertyWriteWithoutResponse= 0x04, // 無(wú)響應(yīng)的寫
    CBCharacteristicPropertyWrite= 0x08,                // 寫
    CBCharacteristicPropertyNotify= 0x10,               // 通知
    CBCharacteristicPropertyIndicate= 0x20,
    CBCharacteristicPropertyAuthenticatedSignedWrites= 0x40,
    CBCharacteristicPropertyExtendedProperties= 0x80,
    CBCharacteristicPropertyNotifyEncryptionRequired= 0x100,
    CBCharacteristicPropertyIndicateEncryptionRequired= 0x200
};

實(shí)際使用

CBCentralManager 中心設(shè)備的使用方法
@interface XIXCBCenterManager : CBCentralManagerDelegate
@property (strong, nonatomic) CBCentralManager * cbcManager;
...
-(void)propertyInit{
...
// 創(chuàng)建 cbcManager 對(duì)象稼钩,創(chuàng)建之后會(huì)調(diào)用代理返回中心設(shè)備狀態(tài)
 self.cbcManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
 
#pragma mark - CoreBlueToothDelegate
// 藍(lán)牙狀態(tài)代理坝撑,藍(lán)牙創(chuàng)建之后會(huì)調(diào)用一次
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{
    switch (central.state) {
        case CBManagerStatePoweredOn:  // 藍(lán)牙狀態(tài)可用
            NSLog(@"藍(lán)牙已打開巡李,可用侨拦,開始掃描");
            // 藍(lán)牙狀態(tài)可用的時(shí)候,開啟掃描可用的藍(lán)牙外設(shè)
                [self.cbcManager scanForPeripheralsWithServices:nil options:nil];
                [MBProgressHUD ShowMBToView:self.view withMessage:@"正在掃描設(shè)備..."];
            break;
        case CBManagerStateUnsupported:
            [MBProgressHUD showError:@"藍(lán)牙不可用"];
            break;
        default:{
            [MBProgressHUD hideHUDForView:self.view];
            UIAlertController * alertVC = [UIAlertController alertControllerWithTitle:nil message:@"請(qǐng)先開啟藍(lán)牙功能" preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction * cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
            UIAlertAction * settingAction = [UIAlertAction actionWithTitle:@"去設(shè)置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action){
                [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
            }];
            [alertVC addAction:cancelAction];
            [alertVC addAction:settingAction];
            [self presentViewController:alertVC animated:YES completion:nil];
        }
            break;
    }
}
 
/**
 掃描到藍(lán)牙外設(shè)后調(diào)用狱从,每掃描到一個(gè)設(shè)備信息返回一次季研,判斷返回的該藍(lán)牙是否是已經(jīng)被添加過的与涡,如果被添加過,則替換數(shù)組中的該設(shè)備豺鼻,如果沒有添加過則添加到數(shù)組
 @param central central
 @param peripheral 掃描到的藍(lán)牙外設(shè)
 @param advertisementData 藍(lán)牙外設(shè)的額外數(shù)據(jù)
 @param RSSI 信號(hào)強(qiáng)度
 */
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(nonnull CBPeripheral *)peripheral advertisementData:(nonnull NSDictionary<NSString *,id> *)advertisementData RSSI:(nonnull NSNumber *)RSSI{
    if (peripheral.name.length <= 0) return;
// 獲取外設(shè)廣播包含的數(shù)據(jù)
    NSData * adverData = advertisementData[@"kCBAdvDataManufacturerData"] ;
   if(adverData != nil){
    AdvertiseMentModel * advModel = [self.connect getAdvertisementData:adverData];
// 如果掃描到的外設(shè)已存在,則替換檩奠,如果不存在則添加埠戳,刷新列表
    if (advModel) {
        PeripheralModel * peripheralModel;
        if (![self.perArray containsObject:peripheral]) {
            [self.perArray addObject:peripheral];
            peripheralModel = [[PeripheralModel alloc] initWithPeripheral:peripheral];
            [self.deviceArray addObject:peripheralModel];
        }
        NSInteger index = [self.perArray indexOfObject:peripheral];
        peripheralModel = self.deviceArray[index];
        peripheralModel.advertisementModel = advModel;
        [self.tableView reloadData];
    }
  }
}
// 使用列表展示外設(shè)整胃,點(diǎn)擊 Cell 的時(shí)候屁使,獲取外設(shè)對(duì)象并連接
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
        self.selectedPeriphalModel = self.resultList[indexPath.row];
         NSDictionary * dic = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey];
        [self.cbcManager connectPeripheral:self.selectedPeriphalModel.periphal options:dic];
        [MBProgressHUD ShowMBToView:self.view withMessage:@"正在連接設(shè)備..."];
// 設(shè)置 10s 的連接超時(shí)時(shí)間,連接超時(shí)則調(diào)用 [self.cbcManager cancelPeripheralConnection:self.selectedPeriphalModel.periphal]; 取消連接
        [self performSelector:@selector(connectTimeOut) withObject:nil afterDelay:10.0f];
}
// 連接成功調(diào)用代理
/**
 連接外設(shè)成功后調(diào)用
 @param central central
 @param peripheral 連接成功的設(shè)備
 */
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
// 沒有超時(shí)則需要取消延遲 調(diào)用方法 同時(shí)停止掃描
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(connectTimeOut) object:nil];
    [self.cbcManager stopScan]; // 停止掃描
    [MBProgressHUD hideHUDForView:self.view animated:YES];
}
/**
 連接失敗調(diào)用
 @param central central
 @param peripheral 連接的設(shè)備
 @param error 錯(cuò)誤原因
 */
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    [MBProgressHUD hideHUDForView:self.view animated:YES];
    [MBProgressHUD showError:@"連接失敗"];
}
// 斷開連接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
    [MBProgressHUD hideHUDForView:self.view animated:NO];
}
CBPeripheral 外設(shè)設(shè)備的方法使用
#define CHARACTER_UUID_WRITE @"5E9BF2A8-F93F-4481-A67E-3B2F4A07891A"
#define CHARACTER_UUID_NOTI @"8AC32D3F-5CB9-4D44-BEC2-EE689169F626"
@property (strong, nonatomic) CBPeripheral * peripheral;
// 保存可寫的特征,寫數(shù)據(jù)的時(shí)候用這個(gè)特征寫入數(shù)據(jù)
@property (strong, nonatomic) CBCharacteristic * writeCharacter;
// 連接上外設(shè)之后調(diào)用 discoverServices 方法及老,查找該外設(shè)所包含的服務(wù)
 [self.peripheral discoverServices:nil];
// 寫入數(shù)據(jù)
  [self.peripheral writeValue:data forCharacteristic:self.writeCharacter type:CBCharacteristicWriteWithResponse];
 
#pragma mark - CBPeripheralDelegate
/**
 查找藍(lán)牙代理的服務(wù),一個(gè)藍(lán)牙外設(shè)可能有多個(gè)服務(wù)
 @param peripheral peripheral
 @param error 錯(cuò)誤信息
 */
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
//    NSString * UUID_str = [peripheral.identifier UUIDString];
//    NSLog(@"UUID:%@",UUID_str);
    if (error) {
        NSLog(@"查找藍(lán)牙服務(wù)出錯(cuò)");
        return;
    }
    // 開始遍歷查找服務(wù),查找每個(gè)服務(wù)的特征
    for (CBService * service in peripheral.services) {
        // 如果知道特征的 UUID 可以在第一個(gè)參數(shù)傳入 UUID 數(shù)組
        NSLog(@"服務(wù)CBUUID:%@",service.UUID.UUIDString);
        [self.peripheral discoverCharacteristics:nil forService:service];
    }
}
// 掃描服務(wù)包含的服務(wù)
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(NSError *)error{
    if (error) {
        NSLog(@"掃描服務(wù)內(nèi)部的服務(wù)失敗");
        return;
    }
    NSArray * services = service.includedServices;
    for (CBService * service  in services) {
        // 如果知道特征的 UUID 可以在第一個(gè)參數(shù)傳入 UUID 數(shù)組
        [peripheral discoverCharacteristics:nil forService:service];
    }
}
// 查找服務(wù)的特征骄恶,一個(gè)服務(wù)可能有多個(gè)特征叠蝇,一個(gè)特征可能有多個(gè)屬性,CBCharacteristic 是一個(gè)枚舉值
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(nonnull CBService *)service error:(nullable NSError *)error{
    if(error){
        NSLog(@"查找特征錯(cuò)誤");
        return;
    }
    for (CBCharacteristic * characteristic in service.characteristics) {
// 這里開發(fā)的時(shí)候和硬件溝通,獲取到了具體的特征的 UUID
        if([characteristic.UUID.UUIDString isEqualToString: CHARACTER_UUID_WRITE]){
            self.writeCharacter = characteristic; // 這是可寫屬性单芜,保存起來后面需要使用它來寫數(shù)據(jù)
        }else if([characteristic.UUID.UUIDString isEqualToString: CHARACTER_UUID_NOTI]){
// 監(jiān)聽該特征洲鸠,外設(shè)發(fā)送通知的時(shí)候才能收到數(shù)據(jù)
             [peripheral setNotifyValue:YES forCharacteristic:characteristic];
         }
    }
}
// 接收通知代理
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    if (error) {
        NSLog(@"錯(cuò)誤:%@",error.localizedDescription);
        return;
    }
    CBCharacteristicProperties  properties = characteristic.properties;
    if (properties & CBCharacteristicPropertyRead) {
        // 如果具備讀特性扒腕,即可讀取特性的 Value 值
        [peripheral readValueForCharacteristic:characteristic];
    }
}
// 讀取 Characteristic 中的值
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error{
    if (error) {
        NSLog(@"讀取數(shù)據(jù)錯(cuò)誤:%@",error.localizedDescription);
        return;
    }
    NSData * dada = characteristic.value;
    if (dada.length <= 0) {
        return;
    }
    Byte * byteData = (Byte*)[dada bytes];
    if (!(byteData[0] == 0x48 && byteData[1] == 0x65 &&byteData[2] == 0x6c &&byteData[3] == 0x6c)) {
         NSLog(@"通知數(shù)據(jù):%@",dada);
    ....
    }
}
// 寫入數(shù)據(jù)的回調(diào)皆的,返回寫入成功失敗結(jié)果
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error{
    if (error) {
        NSLog(@"寫入數(shù)據(jù)失敗");
    }
}

中心設(shè)備就是主動(dòng)去連接別的設(shè)備的設(shè)備蹋盆,一般就是手機(jī)栖雾,外設(shè)就是發(fā)送廣播析藕,被連接的設(shè)備,一般就是手環(huán)或者包含藍(lán)牙模塊的硬件設(shè)備
CoreBlutooth 已經(jīng)把 ble 封裝的很好竞慢,只需要順著代理方法一步一步從掃描到連接到數(shù)據(jù)讀寫都可以完成梗顺。

另外: 藍(lán)牙廣播的數(shù)據(jù)長(zhǎng)度大小有限制寺谤,不能通過廣播獲取外設(shè) MAC 地址來區(qū)分变屁,所以區(qū)分多個(gè)外設(shè)的話就需要在廣播的 kCBAdvDataManufacturerData 字段的數(shù)據(jù)里面包含粟关, advertisementData 數(shù)據(jù)包含多個(gè)字段环戈,大家可以在使用的時(shí)候打印看看院塞,具體的字段對(duì)數(shù)據(jù)的類型也有限制拦止,服務(wù)糜颠、特征其兴、描述都是對(duì)應(yīng)的 uuid 來唯一標(biāo)識(shí)夸政,如何知道 UUID 的話直接使用 UUID 能免去多層遍歷秒梳。
酪碘。

原文鏈接:https://blog.csdn.net/u012439446/article/details/105484097
更多參考: http://www.reibang.com/p/927ef9d5d2d1

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末徙赢,一起剝皮案震驚了整個(gè)濱河市狡赐,隨后出現(xiàn)的幾起案子枕屉,更是在濱河造成了極大的恐慌搀擂,老刑警劉巖哨颂,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件威恼,死亡現(xiàn)場(chǎng)離奇詭異箫措,居然都是意外死亡蒂破,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門喇伯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人买喧,你說我怎么就攤上這事捻悯。” “怎么了淤毛?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵今缚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我低淡,道長(zhǎng)姓言,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任蔗蹋,我火速辦了婚禮何荚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘猪杭。我一直安慰自己餐塘,他們只是感情好皂吮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布候齿。 她就那樣靜靜地躺著,像睡著了一般亚皂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上跟衅,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天,我揣著相機(jī)與錄音雇初,去河邊找鬼辩蛋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛绞愚,可吹牛的內(nèi)容都是我干的糖驴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼定嗓,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼奸绷!你這毒婦竟也來了辛块?” 一聲冷哼從身側(cè)響起胞谈,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤午阵,失蹤者是張志新(化名)和其女友劉穎惧眠,沒想到半個(gè)月后呆盖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盆繁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年拦惋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了言秸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片破加。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖玫锋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布仿贬,位于F島的核電站幽勒,受9級(jí)特大地震影響击吱,放射性物質(zhì)發(fā)生泄漏鞋仍。R本人自食惡果不足惜党瓮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一割坠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蚓哩,春花似錦、人聲如沸稿茉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)怠蹂。三九已至嫌佑,卻和暖如春为肮,著一層夾襖步出監(jiān)牢的瞬間重斑,已是汗流浹背骨稿。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工坦冠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留形耗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓辙浑,卻偏偏與公主長(zhǎng)得像激涤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子例衍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355