How does getBytes:length method work?

I am working on a Core Bluetooth Application in which I programmed a peripheral device to transmit a heart rate value of 90 beats per minute. On iOS, I have discovered my service and my heart rate characteristic and am now working on processing that data, from the console output, the raw NSData characteristic.value data is 5a000000 which translates to a unsigned 32 bit integer value of 90 beats per minute. What I want to do now is to take that NSData data and turn store it as an unsigned 32 bit value, I found a reference online to the getBytes:length: method on the NSData class, however, I dont understand how that line of code works, and how that data is actually converted to my uint32_t value. Any explanation on how the method works and what is it doing when I provide it my rawData would we helpful!


        NSData *rawData = characteristic.value;
  
        uint32_t rawInt = 0;
        NSLog(@"rawInt Value Before: %d", rawInt);
    
        // THIS METHOD IS WHAT I DONT UNDERSTAND
        // NSLOG VALUES INDICATE THAT THE METHOD IS WORKING SINCE I GOT THE CORRECT HEART RATE VALUE
        [rawData getBytes:&rawInt length:sizeof(rawInt)];
        NSLog(@"rawInt Value After: %d", rawInt);
-getBytes:length:
is like
memcpy
; it just transfers raw bytes out of the buffer held by NSData into the destination address that you give it. In this case there’s 4 bytes in the buffer that are meant to be interpreted as a
uint32_t
. A naïve implementation might look like this:
- (void)getBytes:(void *)buffer length:(NSUInteger)length {
    memcpy(buffer, self.bytes, min(length, self.length));
}

The only gotcha here is endianness. I believe that Bluetooth LE uses little endian values, so a raw

memcpy
like this works on current hardware (which is little endian). However, you really should write your code to be independent of endianness, so you’d want to finish up with something like this.
rawInt = OSSwapLittleToHostInt32(rawInt);

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
        NSData *rawData = characteristic.value;
        uint32_t rawInt = 0; 
        [rawData getBytes:&rawInt length:sizeof(rawInt)];
        NSLog(@"rawInt Value After: %d", rawInt);

In simple terms -----

Because "rawInt" is defined as a "uint32_t" the value of sizeof(rawInt) will be 4 (i.e. 4 bytes). You are asking the class NSData to "getBytes" from rawData - you are asking it to get the first 4 bytes from the hex value 5a0000000 and it is returning "5a" which is the first 4 bytes. Then you are asking NSLog to convert that to a double (%d) value. 5*16=80 and "a" is 10.

Okay, that makes sense. Thanks for the explanation on what the method is doing, however, I still don't understand how the rawInt value is now the value of 90 beats per minute, I just used the NSLog statement to make sure I was getting the number. In the next step after this, I do the following:


        // Turn rawInt to an NSNumber and then NSNumber -> string to display in UILabel
        self.heartRateValue = [[NSNumber alloc]initWithUnsignedInt:rawInt];
        [self.heartRateLabel setText:[self.heartRateValue stringValue]];


My confusion still lies as to where the conversion from "5a0000000" to "90" is taking place in the getBytes method!

There is no "5A000000" value in the Data object, just a sequence of 8-bit values (5a, 0, 0, 0 in this case, one per byte). When you copy those 4 bytes into the memory occupied by the uint32_t variable (also 4 bytes long), and reference that variable in your code, the memory is taken to be a single 32-bit value. At that point, the value will appear to be (in hex) "0000005a", because the original bytes were in little-endian order.

What's confusing here is that if you NSLog the value of the data object, it displays the bytes in groups of 4. It looks as if the "5a" is in the high-order byte of a 32-bit value, but it's really just the left-most byte of a 4-byte sequence, which is the low-order byte in little-endian order. You have to be really, really careful when examining raw bytes that are "grouped" into 2-, 4- or 8-byte clumps.

My understanding is off by a factor of 4, but basically - The "getBytes: length:4" reduces 5a000000 to 5a. The "&" in "&rawInt" places the resulting data value "5a" (aka 5=0101 and a=1010 so 01011010) into rawInt and puts lots of 0's before it.


Another question might be "why does '5a' = 90?" (Forgive me if this is obvious to you.)

The answer to that question is 'base 16'. The 9 in 90 is 'base 10'. 9*10=90. The 5 in 5a is in base 16. Converting to base 10: 5*16=80. The a is also base 16 and it equals 10 (the number after 9). In base 10: 80+10=90.



EDIT - overlapped 'correct answer above'. I have no idea what a little endian is.

I have no idea what a little endian is.

It’s quite important to understand the concept of endianness if you’re dealing with any sort of low-level data structure. I recommend a quick trip to The Font of All Wisdom™.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Okay, I kind of stopped working on this code for a while and am back at it now, so I guess I made an error in my thinking:


1. How is that piece of data (5a000000) represented in memory at the following:

NSData *rawData = characteristic.value;


2. How is that data represented in memory at the end of the following:

uint32_t rawInt = 0;
        NSLog(@"rawInt Value Before: %d", rawInt);

        // THIS METHOD IS WHAT I DONT UNDERSTAND
        // NSLOG VALUES INDICATE THAT THE METHOD IS WORKING SINCE I GOT THE CORRECT HEART RATE VALUE
        [rawData getBytes:&rawInt length:sizeof(rawInt)];


3. The next portion of the code is to take that integer value and turn it into an NSNumber value and then a string. So is this the stage when value 90 beats per minute come into play and at all the stages before it was just bytes?


self.heartRateValue = [[NSNumber alloc]initWithUnsignedInt:rawInt];
[self.heartRateLabel setText:[self.heartRateValue stringValue]];

It might be nice if you started a new thread for this. Although this is a continuation of the earlier discussion, it's confusing to find what's new now.


Keep in mind that (8-bit) bytes are the lingua franca of data representation. That is, underlying every form of data is a sequence of bytes, or a collection of sequences of bytes.


1. Within the NSData object is a 4-byte representation of the input value, which looks like this:


5A, 00, 00, 00


Note that the order I wrote them is significant in the sense that successive bytes are in successively increasing addresses.


2. The rawInt variable also has a 4-byte representation of its value, which looks like this:


5A, 00, 00, 00


Since you copied it here from the NSData object, this copy of the data starts at a different address, but it's the same sequence of 4 bytes. However, for the purposes of (say) logging the value of rawInt using the %d format, the hardware interprets these 4 bytes as a single 32-bit number. Since Intel CPUs are "little endian", this number is 0000005A, or 90 in decimal. Note that the fact that the "5A" is at the end now is merely a detail of how we write numbers. There's no different in the bytes themselves.


3. In source code terms, we keep our sanity by treating rawInt as a single 32-bit number (value 0000005a) rather than a sequence of 4 8-bit bytes (5A, 00, 00, 00). The value is already 90 before you convert it to a NSNumber or NSString.


Note that "convert" in this context means creating a entirely new object (a NSNumber or NSString) whose value happens to represent 90. We don't know whether those objects contain 5A, 00, 00, 00. Indeed, there's a good chance that they don't, for reasons that aren't relevant to this particular discussion.


The takeaway here is that the meaning of a sequence of bytes depends on the order in which the bytes are interpreted, and that can in turn depend on how many bytes are being interpreted. That particular detail arises from the CPU hardware design.

Thanks, and just a curious question to make sure I am understanding this properly:

[rawData getBytes:&rawInt length:sizeof(rawInt)];


So as I understand it is that we are send a message to rawData variable to to give the data it has inside so we can copy that data into our rawInt variable?

Specifically, you are sending a message to the object referenced by the rawData variable to copy the data inside it to your rawInt variable on your behalf.


That's almost what you said, but not exactly the same. (As I mentioned in the new thread, you can ask the object for a pointer to the data so that you can copy it yourself, but having the object copy the data for you is safer, and less code.)

Trying to work out this level of detail without a sold understanding of the issues involved is a recipe for frustration. One of the problems you are going to encounter is that Xcode and other tools alread know about endianness. If you try to debug and inspect the data structures, they may do the byte swapping for you and just confuse you even more.


Eskimo's first response was the correct one. Don't try to figure it out. Just copy the bytes from your NSData into a pointer to a 32-bit integer. If you know you are being sent a 4-byte little endian, then call OSSwapLittleToHostInt32 on that integer. If you are being sent big endian (also known as network byte order), then call OSSwapBigToHostInt32.


Depending on what platform you are running on, those operation might not do anything. If the data already is little endian and you are on a little endian machine, there is nothig to do. But you don't want to depend on that. You can never assume the endianness of your CPU. You can, however, be confident in the endianness of a well-defined protocol. So, just call the function and it will handle it.

How does getBytes:length method work?
 
 
Q