iOS之BLE藍牙SDK開發我的總結(進階篇)

有關iOS BLE藍牙基礎功能的封裝已經在上篇文章寫完了,本篇文章負責把在SDK封裝過程當中遇到的問題知識點進行總結。html

封裝SDK實質上是把一些功能給封裝成一個個對應的方法,用SDK的人只須要調用相應的方法就能實現對應的功能,而再也不須要一個複雜的實現過程。算法

藍牙功能的實現實質上是經過手機和藍牙互相通訊而創建的,因此通訊的協議是由咱們本身進行擬定的。解釋一下協議的擬定,就是手機端和設備端提早商量好用某些字符表明某種意義,能夠理解爲手機端和設備端二者之間創建了一種特殊的語言。(好比:12345表明讓設備自爆,那麼設備收到12345的時候就自爆了😂舉個例子)。編程

好了,如今進入正題

既然有通訊協議,那麼爲了安全考慮就必定須要加密傳輸,不然隨便來我的給設備發個12345。。。。不就完了。數組

下面就是第一個問題

  1. 有祕密,就確定須要密鑰進行加解密,可是密鑰又不能直接發送,不然被截取了密鑰和沒有密鑰有啥區別。

因而咱們採用DH協商密鑰的方法進行計算密鑰。想了解什麼是DH協商密鑰的能夠看看這個DH協商密鑰原理DH密鑰計算方法安全

計算DHKey須要進行超大數的計算,在百度上搜索了半天終於找到了一個比較好的大數計算的第三方庫😢,放到了私人百度雲上JKBigInteger,密碼:ub2s。須要的可進行下載。協商密鑰方面的事從找到這個庫的時候起就沒什麼大問題了。bash

  1. 有了密鑰,接下來就是加密方法了。目前爲止比較主流的加密方式就是aes,md5,base64等了,這個SDK就是使用aes和md5混合加密的形式進行數據的傳輸。

接下來就是aes加密相關的分享了,-->Aes加密算法主要仍是使用iOS系統自帶的加密算法,在系統提供的算法上進行了一層包裝,用起來更方便。網絡

一樣的md5的代碼不是太多就直接貼出來了app

//md5加密字符串
+ (NSString *)md5WithString:(NSString *)inputStr
{
    //傳入參數,轉化成char
    const char *str = [inputStr UTF8String];
    //開闢一個16字節(128位:md5加密出來就是128位/bit)的空間(一個字節=8字位=8個二進制數=2個16進制數)
    unsigned char md[CC_MD5_DIGEST_LENGTH];
    /*
     extern unsigned char * CC_MD5(const void *data, CC_LONG len, unsigned char *md)官方封裝好的加密方法
     把str字符串轉換成了32位的16進制數列(這個過程不可逆轉) 存儲到了md這個空間中
     */
    CC_MD5(str, (CC_LONG)strlen(str), md);
    //建立一個可變字符串收集結果
    NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH];
    for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
    {
        /**
         X 表示以十六進制形式輸入/輸出
         02 表示不足兩位,前面補0輸出;出過兩位不影響
         printf("%02X", 0x123); //打印出:123
         printf("%02X", 0x1); //打印出:01
         */
        [ret appendFormat:@"%02X",md[i]];
    }
    
    //返回一個長度爲32的字符串
    if (!ret || [ret length] == 0)
    {
        return nil;
    }
    return ret;
}
複製代碼
//md5加密data數據
+ (NSString *)md5StringWithData:(NSData *)data
{
    //1: 建立一個MD5對象
    CC_MD5_CTX md5;
    //2: 初始化MD5
    CC_MD5_Init(&md5);
    //3: 準備MD5加密
    CC_MD5_Update(&md5, data.bytes, (CC_LONG)data.length);
    //4: 準備一個字符串數組, 存儲MD5加密以後的數據
    unsigned char result[CC_MD5_DIGEST_LENGTH];
    //5: 結束MD5加密
    CC_MD5_Final(result, &md5);
    NSMutableString *resultString = [NSMutableString string];
    //6:從result數組中獲取最終結果
    for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
        [resultString appendFormat:@"%02X", result[i]];
    }
    return resultString;
}
複製代碼

兩個方法,一個是用來加密utf8編碼的字符串的,一個是用來加密NSData類型數據的。一樣是使用系統提供的庫,因此使用時需導入#import <CommonCrypto/CommonDigest.h>系統頭文件。ui

至此,有關協議擬定方面的問題就沒什麼問題了。編碼

----------------------這是一條分界線--------------------------

有密鑰,有加密傳輸數據的方法,接下來就是數據來源了。

由於數據的格式有不少種,而在手機和藍牙之間進行傳輸的卻只有一種--NSData,也就是二進制數據,所以咱們就須要設計一套通用性的方法能把各類數據轉換成NSData類型--------也就是俗稱的編碼了。

廢話很少說,直接上代碼了

+ (NSData *)dataWithByte:(Byte)byte
{
    NSData *data = [NSData dataWithBytes:&byte length:sizeof(Byte)];
    return data;
}
+ (NSData *)dataWithShort:(short)Short
{
    HTONS(Short);
    return [NSData dataWithBytes:&Short length:sizeof(short)];
}
+ (NSData *)dataWithInt:(int)Int
{
    HTONL(Int);
    return [NSData dataWithBytes:&Int length:sizeof(int)];
}
+ (NSData *)dataWithLong:(long)Long
{
    HTONLL(Long);
    return [NSData dataWithBytes:&Long length:sizeof(long)];
}
+ (NSData *)dataWithString:(NSString *)string
{
    return [string dataUsingEncoding:NSUTF8StringEncoding];
}
+ (NSData *)dataWithHexString:(NSString *)str
{
    if (!str || [str length] == 0)
    {
        return nil;
    }
    NSMutableData *hexData = [[NSMutableData alloc] initWithCapacity:0];
    NSRange range;
    if ([str length] % 2 == 0)
    {
        range = NSMakeRange(0, 2);
    }
    else {
        range = NSMakeRange(0, 1);
    }
    for (NSInteger i = range.location; i < [str length]; i += 2)
    {
        unsigned int anInt;
        //取出range內的子字符串
        NSString *hexCharStr = [str substringWithRange:range];
        //掃描者對象,掃描對應字符串
        NSScanner *scanner = [[NSScanner alloc] initWithString:hexCharStr];
        //掃描16進制數返回給無符號整型anInt
        [scanner scanHexInt:&anInt];
        //把這個int類型數轉成1個字節的NSdata類型
        NSData *entity = [[NSData alloc] initWithBytes:&anInt length:1];
        [hexData appendData:entity];    //加到可變data類型hexData上
        
        range.location += range.length;
        range.length = 2;
    }
    return hexData;
}
複製代碼

值得注意的是上面的HTONS(Short);HTONL(Int);HTONLL(Long);這3個東西,可能第一次見的時候不明白是什麼意思。解釋一下,HTON指的是Host To Network即主機字節順序轉化爲網絡字節順序。不懂的能夠看看這篇文章scoket編程。至於最後一位就好理解了,S表示short類型,L表示int類型,LL表示long類型。

細心可能會發現,不對啊,你這少了浮點數類型,要是我想傳輸浮點數怎麼辦??

額,不得不說OC想要讓float和NSData類型互轉仍是挺很差弄的,沒有直接的轉化方法,若是按照上面的那幾種方法類比的話結果是不對的。OC很差弄,沒問題,C語言能夠弄。下面是代碼:

typedef float type_f32;
typedef unsigned char type_u8;
typedef unsigned short type_u16;

typedef union
{
    type_f32 f_val;
    type_u8 c[4];
} float_u;

type_f32 STREAM_TO_FLOAT32_f(type_u8* p, type_u16 offset)
{
    float_u f;
    f.c[0] = p[offset + 3];
    f.c[1] = p[offset + 2];
    f.c[2] = p[offset + 1];
    f.c[3] = p[offset];
    return f.f_val;
}

type_u8* FLOAT32_TO_STREAM_f(type_u8* p, type_f32 f32)
{
    float_u f;
    f.f_val = f32;
    p[0] = f.c[3];
    p[1] = f.c[2];
    p[2] = f.c[1];
    p[3] = f.c[0];
    return p;
}
複製代碼

上面使用了C語言的聯合體,,等等,結構體我知道,聯合體是個什麼玩意,對於學OC的咱們來講可能還真的不清楚C語言的聯合體是什麼?下面簡單解釋一下:

聯合體就是定義了兩種不一樣類型的變量,如上type_f32 f_valtype_u8 c[4]使得這兩個不一樣類型的變量共享同一塊地址空間。type_f32實質上就是float類型,type_u8實質上是char類型,這個聯合體就是讓一個float類型的數f_val和一個字符數組c[4]共享同一個地址空間,而後提供了兩個從空間種取出不一樣類型數據的方法。至於上面的數組的順序是0123,仍是3210這個要看硬件端是怎麼寫的了。

接下來就簡單了,只須要把那兩個C語言方法封裝成OC的方法就好了,以下:

+ (NSData *)dataWithFloat:(float)Float
{
    Byte byte[4];
    FLOAT32_TO_STREAM_f(byte, Float);
    return [[NSData alloc] initWithBytes:byte length:sizeof(float)];
}
+ (float)floatWithData:(NSData *)data
{
    Byte *byte = (Byte *)[data bytes];
    float b = STREAM_TO_FLOAT32_f(byte, 0);
    return b;
}
複製代碼

至於解碼的問題就很少說了,解碼int類型。供參考

//model.rand
int rand;
//意爲從receiveData裏面取出第21,22,23,24這4個字節的數據,賦值給rand
[receiveData getBytes:&rand range:NSMakeRange(20, 4)];
HTONL(rand);
model.rand = rand;
複製代碼

到如今爲止,封裝SDK功能的準備工做已經作完了。

接下來的內容就是記錄一下本人在寫SDK中遇到的一些問題和解決方法。是沒有源碼的。

  1. SDK要求全部功能都具備一個success和一個failure回調以及一個判斷超時的timer。調用一次功能方法只會執行上面3種結果中的一個。即當success或failure執行時要把這些都給清除保證不會二次執行。一樣timer執行時也是同樣。
  2. 每一次調用方法都會獲得一個結果,而且不能發生結果錯亂(好比連續調用一個方法兩次,第一次的結果不能調用第二次的回調)。

綜上,我須要把每次寫數據對應的方法id,success,failure,timer保存起來,等收到設備的回覆時再根據方法id找到保存的方法對應的success等,根據回覆的數據再判斷要調用哪一個回調。而且須要注意的是調用完成後要把這一整條數據都清空。

總結下來就3點

  • 保證不會發生回調覆蓋
  • 保證不會發生回調錯亂
  • 保證回調不會屢次執行

好了,這些就是我作的Ble藍牙SDK時遇到的比較有意思的問題了。

相關文章
相關標籤/搜索