1. 程式人生 > >iOS藍牙通信數據處理,位運算,數據的大小端轉換

iOS藍牙通信數據處理,位運算,數據的大小端轉換

make 位數 存儲 大小端模式 計算 取出 sign nsstring nta

目的

轉載自:http://blog.csdn.net/remember_17/article/details/77337534?locationNum=10&fps=1

在藍牙項目的開發過程中,會遇到了一些與數據處理有關的問題,本文對這些問題進行了基本的整理並分享給大家。包含如下三個方面的內容。

  1. 數據大小端的介紹
  2. 大小端數據模式的轉換
  3. 按位運算,左移、右移運算

一、數據大小端的介紹

網上關於數據大小端的介紹一大堆,為了讓文章全面點,本文也就這方面簡單說明一下。

a. 大小端表示數據在計算機中的存放順序。
b. 大端模式符合人類的正常思維,高字節保存在內存的低地址。
c. 小端模式方便計算機處理,高字節保存在內存的高地址。
d. iOS中默認的是小端存儲。

技術分享圖片

你可以在Xcode中運行下面這兩行代碼,就會打印出大小端模式。

short int number = 0x8866;
NSLog(@"%@",[NSString stringWithFormat:@"%x",((char *)&number)[0]].intValue == 66 ? @"小端模式" : @"大端模式");

二、大小端數據模式的轉換

藍牙通信的時候,從硬件接收到的數據是NSData類型,我們需要對數據進行解析才能拿到真正方便使用的數據。
但是接收到的數據在內存中的保存順序可能與我們希望的相反,所以在解析的過程中就涉及到了大小端的轉換問題。

其實iOS的大小端轉換非常方便,在蘋果的Core Fundation中就提供了進行這些數據處理的方法。Apple官方文檔

下面我就舉幾個例子,一起來看一下Fundation中與大小端有關方法的基本使用。

1、CFByteOrderGetCurrent()
返回當前電腦的大小端模式

CFByteOrderGetCurrent()
返回的值是一個如下的枚舉
enum __CFByteOrder {
    CFByteOrderUnknown,       // 未知的
    CFByteOrderLittleEndian,  // 小端模式
    CFByteOrderBigEndian      // 大端模式
};

2、CFSwapInt16()
轉換一個16位的整型數字

// 把數字15轉換模式
CFSwapInt16(15)

// 上面運算得到的結果十進制為3840,十六進制為0xF00。
// 而0xF00反轉過來就是0xF = 15,所以證明這個方法確實對15進行了反轉。

3、CFSwapInt16BigToHost()

把一個16位的整型數字從大端模式轉為本機數據存放模式。如果本機為大端模式,則原值不變。

// 把大端模式的數字Number轉為本機數據存放模式
CFSwapInt16BigToHost(Number)

4、CFSwapInt32HostToBig()
把一個32位本機模式數據轉換為大端模式。如果本機為大端模式,則原值不變。

// 把本地存儲模式的數字Number轉為大端模式
CFSwapInt32HostToBig(Number)

還有好多方法(詳見官方文檔),基本都是大同小異,從字面就可以理解它的用法。

通常能用到的也就那麽兩三個。
一般需求是把大端轉成本地模式,也就是小端模式。
CFSwapInt16BigToHost
CFSwapInt32BigToHost

下面是封裝好了的兩個方法,在開發中可以直接用來解析數據。
兩個方法分別返回Signed和Unsigned類型的數據。
代碼中的location代表準備解析的數據的位置,offset代表需要解析幾位。
* 需要註意的是,當僅僅是解析1位數據的時候,就不需要使用像CFSwapInt16BigToHost這樣的方法了,具體可以查閱代碼。

// 轉為本地大小端模式 返回Signed類型的數據
+(signed int)signedDataTointWithData:(NSData *)data Location:(NSInteger)location Offset:(NSInteger)offset {
    signed int value=0;
    NSData *intdata= [data subdataWithRange:NSMakeRange(location, offset)];
    if (offset==2) {
        value=CFSwapInt16BigToHost(*(int*)([intdata bytes]));
    }
    else if (offset==4) {
        value = CFSwapInt32BigToHost(*(int*)([intdata bytes]));
    }
    else if (offset==1) {
        signed char *bs = (signed char *)[[data subdataWithRange:NSMakeRange(location, 1) ] bytes];
        value = *bs;
    }
    return value;
}

// 轉為本地大小端模式 返回Unsigned類型的數據
+(unsigned int)unsignedDataTointWithData:(NSData *)data Location:(NSInteger)location Offset:(NSInteger)offset {
    unsigned int value=0;
    NSData *intdata= [data subdataWithRange:NSMakeRange(location, offset)];

    if (offset==2) {
        value=CFSwapInt16BigToHost(*(int*)([intdata bytes]));
    }
    else if (offset==4) {
        value = CFSwapInt32BigToHost(*(int*)([intdata bytes]));
    }
    else if (offset==1) {
        unsigned char *bs = (unsigned char *)[[data subdataWithRange:NSMakeRange(location, 1) ] bytes];
        value = *bs;
    }
    return value;
}

三、按位運算,左移、右移運算

在講解位運算和左右移之前,先來回憶回憶基本的數據計量單位。

1字節是一個8位的數據,可以代表從0-255共256個數字。
1B(byte,字節)= 8 bit(位)。

模擬一次解析數據的過程:

  1. 假如藍牙每次發過來的數據大小為32個字節,這個數據在NSData類型下Log出來是這個樣子:<0aa60000 00000000 00000000 00000000 00000000 00000059 9db56800 00260b01>

  2. 每兩個數字表示一個十六進制的數據,例如最左邊的0a代表了一個字節,也就是0x0A = 10。

  3. 現在我們要截取最左邊的0aa6這兩個字節(16位),這個數據是UInt16類型,那麽首先要做的就是運用上面封裝好了的大小端轉換方法來截取這兩個字節,下面代碼中的result就是所需要的數據。

// 從第0位開始,截取2個字節,所以location是0,offset是2
UInt16 result = [self unsignedDataTointWithData:data Location:0 Offset:2];

可是拿到result之後工作還沒有結束。
* 需求:result的二進制是0000 1010 1010 0110,一個16位的數字,假如與硬件工程師提前說好了,低4位(0110)代表組數,5-8位(1010)代表每組的人數。
如何分別拿出所需的數據呢?

這時候,位運算就派上用場了。
一起來看看位運算和左右移的基本使用方法和情景,需求的答案也在其中。

1、按位與 &

同為1為1,否則為0

例如:3 & 5
0000 0011
0000 0101
0000 0001 = 1
所以 3 & 5=1

特點:
(1)清零:任何數和0相與,結果為0.
(2)取出指定位的值。取哪一位,就把對應的位定為1。

例如:
拿到了一個16位的數據result = 0000 1010 1010 0110,如何拿到這個數據的低4位呢?
就可以使用按位與,代碼如下

// 0x000f == 0000 0000 0000 1111
// 按位與上result之後,得到的number == 0000 0000 0000 0110 就是低4位的數據0110
int number = result & 0x000f;

2、按位或 |

只要有一個為1就為1
負數按補碼的形式參加按位或運算

例如:3 | 5
0000 0011
0000 0101
0000 0111 = 7
所以 3 | 5=7

特點:
(1)對數據的某些位置1。

例如:
將X=1010 0000的後四位置1
1010 0000
0000 1111
1010 1111
這樣後4位就全為1了

3、異或運算 ^

如果對應的位不同則為1,相同為0

例如 3 ^ 5
0000 0011
0000 0101
0000 0110
所以 3 ^ 5= 6

特點:
(1)特定位翻轉,哪一位需要翻轉就把對應的位設置為1
(2)任何數和0異或,原值不變。
(3)異或運算可以交換位置:3 ^ 5 ^ 6 == 3 ^ 6 ^ 5
(4)相同的數異或等於0:9 ^ 9 == 0
(5)a ^ b ^ a == b

4、取反 ~

0變1,1變0

例如 ~3
0000 0011
1111 1100

特點:
(1)配合按位與把一個數的最低位設置為0
例如:
把X=1011 0111按位與(~1)
X & (~1) = 1011 0110
這樣最後一位就為0了

5、左移運算 <<

二進制位全部左移若幹位,左邊的丟棄,右邊補0

例如 3<<2
0000 0011 = 3
0000 1100 = 12 (左移後)
左移3<<2 == 12

特點:
若左移時舍棄的最高位不包含1,則每左移一位,就乘以一次2.
所以a<

6、右移運算 >>

二進制右移若幹位,正數左邊補0,負數左邊補1,右邊丟棄。

例如 12>>2
0000 1100 = 12
0000 0011 = 2 (右移後)
右移12>>2 == 3

特點:
每右移一位,就除以一次2.
a>>n 就是 a除以2的n次方

例如:
繼續用上面按位與的例子,
拿到了一個16位的數據result = 0000 1010 1010 0110,如何拿到這個數據的5-8位呢?
首先運用按位與把5-8位之外的數據全部置0,然後用右移來拿到具體數值。
代碼如下

// 0x00f0 == 0000 0000 1111 0000,result按位與0xf0之後,結果為0000 0000 1010 0000
// 然後右移4位,得到最終所需要的數據number == 0000 0000 0000 1010
int number = (result & 0x00f0) >> 4;


iOS藍牙通信數據處理,位運算,數據的大小端轉換