bytes property on NSData

I'm working on a Bluetooth App, where I have to store the 8Bit values from the sensor into a byte array like so:


- (void) getDataFromSensor: (NSData *)tempData {


   const Byte *rawSensorBytes = [tempData bytes];


}


The confusion I am having is with the specific line [tempData bytes]. So, I know that tempData is the NSData variable and bytes is a property on NSData. According to the developer doc, the byte property is "A pointer to the receiver’s contents." I have heard of this concept of reciever before, but havent really paid attention to it, does the definition basically mean this byte property just points to the the data that is stored somehwere in memory? Also, if anyone could explain receiver that would be great!


Thanks!

Answered by QuinceyMorris in 244342022

>>Firstly, the way I explained everything here, is it correct?


Yes, it looks technically correct to me.


>> Also, in the method call when I pass characteristic.value, am I actually passing in the actual value or the address to that value?


"characteristic" refers another object, of class CBCharacteristic, so we have 3 blocks of memory involved, 2 instances of classes, and a "plain" block of memory with the raw data. The CBCharacteristic instance contains a reference to the NSData instance, which in turn contains a pointer to the raw data.


Because CBCharacteristic is an Obj-C class, and instances of classes are passed (in, say, function parameters) as references, not by copying their data. That's why such classes are called "reference types" — any variable of that type is actually a reference to the instance — to distinguish them from "value types", where a variable of that type contains the actual data.


Those terms are the most useful, and are what Swift uses. The traditional Obj-C terminology would be "class type" and "scalar type", since things that are passed around by value tend to be simple scalar values such as integers and doubles.


Finally, that's why variables referring to class instances in Obj-C have a "pointer-to" asterisk in their declaration (e.g. "CBCharacteristic*"), though the compiler knows that without you telling it. It just reinforces the fact that they are references. (In C++, just to confuse the issue, class instances can be created as value types or reference types.)

It's just a piece of jargon from Object-Oriented programming. When you have an object whose behavior you want to use, you don't "call a function" as in non-OO programming, but "send it a message". In this case, you're sending a "bytes" message to the object referenced by "tempData". That object is the receiver.


(We often cheat and say that "tempData" is the receiver, but we really mean the object that the variable "tempData" refers to.)

>does the definition basically mean


Yes, basically.


'receiver' is a data reference for the (named) variable.

Thanks for the explanation! That cleared it up. Anotehr question I have is regarding a bit more of the whats going on behind the scenes with that messaging statement:


[tempData bytes];


So, in my case, the bytes property is pointing to the address of the temperature data from my sensor in the phone's memory somwhere. Does this mean that the address of the data is stored in the array or the data itself is then somehow by the system fetched from the address location and stored in the array?


My goal is populate this Byte array with those 8 Bit values and then construct the full 16Bit value from the values in the array. After this, I do the following:


/*  make our 16bit variable
    take our msb which at index 1 (cpu puts in lsb by default)
    shift that by 8 bits
    add lsb at index 0
*/
uint16_t roomTemp = (rawSensorBytes[1] << 8) + rawSensorBytes[0];

>> Does this mean that the address of the data is stored in the array or the data itself is then somehow by the system fetched from the address location and stored in the array?


The address that's returned by "bytes" is a simple pointer to an array of bytes, that you can index with [0], [1], etc as in your code sample.


What's going on behind the scenes is that a NSData object is represented by a (fairly small) block of memory for the object itself (e.g. it has the "length" property stored there). In addition, a second block of memory, at least as big as the actual data bytes, is allocated, and its pointer is stored in the object as the "bytes" property. Note that this implies you need to be careful to avoid buffer overflow bugs when using this pointer.

Okay, so that makes sense, I have one more question pertaining to the syntax:

const Byte *rawSensorBytes = [tempData bytes];


So I'm still learning the Objecive-C language and I know that the square brackets are used for sending messages, but in this case are the square brackets indicating an array or a message or perhaps a message inside the array?


Sorry for the follow-ups, just trying to understand some concepts here! Thanks in advance!

No, the square brackets in this case have nothing to do with arrays, they're just part of the message-sending syntax.

Thanks for that clarification. I thought it was an array the whole time, because based on that assumption I thought we were accessing indices of that array when I did the next step after that particular line:


// Step 2: Reconstruct the 16Bit data from the 8 Bits in the array
// array      [lsbRoom, msbRoom, lsbObject, msbObject]
//                 ^        ^         ^         ^
//  index          0        1         2         3

uint16_t room = (rawSensorBytes[1] << 8) + rawSensorBytes[0];


So when I write rawSensorBytes[1], what is actually happening, I thought I was accessing index 1 of the array.

I'm sorry, I misunderstood your question. The square brackets — subscript operator — following the pointer name:


     rawSensorBytes[1]


do indicate an array behavior (and have nothing to do with messaging). This piece of syntax is pure C, while the use of brackets for sending messages is pure Obj-C, but there's really never any confusion, because what's inside the brackets is a lot different.


FWIW, arrays in C are slightly fictional. When you use a subscript operator, you're actually telling the compiler to do pointer arithmetic. (Multiply the subscript by the size of each element, add the result to the base pointer, and access an element indirectly through the computed pointer.)


For your amusement, I'll add that Obj-C 2.0 — the version everyone uses nowadays — also has a subscript operator using […], which can be applied to NSArray objects. When it encounters one of these, the compiler doesn't do pointer arithmetic, but instead sends a predefined message to the object with the subscript value as a parameter. This has nothing to do with the code you're writing, but it's a good example of how languages get more abstract over time. Instead of […] meaning one thing at the machine level, it turns into a semantic concept that the compiler resolves according to context.

Thanks.


So I have another question with regards to the same method below, here it is fully completed:


- (void) getDataFromSensor: (NSData *)temperatureData
{


   // getting pointer to the data
   const Byte *rawSensorBytes = [temperatureData bytes];

   // Reconstruct the 16Bit data from the 8 Bits chunks
   uint16_t room = (rawSensorBytes[1] << 8) + rawSensorBytes[0];
   uint16_t object = (rawSensorBytes[3] << 8) + rawSensorBytes[2];

    //uint -> NSNumber data type
   self.roomTempValue = [[NSNumber alloc]initWithFloat:rawRoomTempToCelcius(room)];
   self.objectTempValue = [[NSNumber alloc]initWithFloat:rawObjectTempToCelcius(object)];

   // NSNumber data -> string that is then set as the text of UILabel
   [self.roomTempLabel setText:[self.roomTempValue stringValue]];
   [self.objectTempLabel setText:[self.objectTempValue stringValue]];

}


The method is then called inside:

- (void) peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {

   if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:IR_TEMPERATURE_DATA_UUID]]) {
    
      // CALL GETDATAFROMSENSOR
      [self getDataFromSensor:characteristic.value];
    
   }
}


So from this I understand that the temperatureData parmater actually holds the value of the characteristic that I am interested in and it makes sense since that value is an NSData type. My question is that inside the implementation of the getDataFromSensor: method, when we send the message bytes, what is actually happening again, I know it was answered earlier, but it seems confusing still. I know that bytes is a pointer to the content, so are we:


sending a message to temperatureData to give it's address in memory (pointer) or is it something else?

There are two pointers involved. One is a reference to the NSData object, and the other is a reference to memory that holds the raw bytes read from the sensor.


The reason there are two pieces of memory is that objects are fixed-size blocks of memory (holding the instance variables, class information and possibly other stuff), but with NSData the amount of raw data is completely variable.


Given the pointer to the NSData object, you (the user of the NSData class) have no idea how to find the pointer to the actual data, and that's why there's a 'bytes' method to give you that pointer.


Underlying all of this, the situation is a bit more complicated. Only in the simplest cases does a NSData object use 'malloc' to get a block of memory for its raw data. In other cases, where it has reason to know that the raw data is memory that's already present (and going to stick around long enough), it keeps a pointer that memory. In other cases, the raw data may be split across multiple blocks of memory. In that case, if you ask for the 'bytes' pointer, the object actually has to copy all of the pieces of data into a single new block of memory, so that the pointer actually works properly in your code. Normally, though, you don't have to worry about any of this, it's all just implementation details.

Okay, this clears up this issue. Now, just a remaining question. So the method here is fully implemented and now what happens is that I call it in the didUpdateCharacteristic method from Core Bluetooth Framework like so:


- (void) peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {

   if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:IR_TEMPERATURE_DATA_UUID]]) {
  
      [self getDataFromSensor:characteristic.value];
  
   }
}


So, at this point, the temperatureData parameter is being used to pass in the characteristic's value which in an NSData type and during this call, when the compiler gets to the line:

const Byte *rawSensorBytes = [temperatureData bytes];


I am getting the address of the characteristic value, because the bytes parameter returns me a pointer to the contents (temperatureData). My questions is:

Firstly, the way I explained everything here, is it correct? Also, in the method call when I pass characteristic.value, am I actually passing in the actual value or the address to that value?

Accepted Answer

>>Firstly, the way I explained everything here, is it correct?


Yes, it looks technically correct to me.


>> Also, in the method call when I pass characteristic.value, am I actually passing in the actual value or the address to that value?


"characteristic" refers another object, of class CBCharacteristic, so we have 3 blocks of memory involved, 2 instances of classes, and a "plain" block of memory with the raw data. The CBCharacteristic instance contains a reference to the NSData instance, which in turn contains a pointer to the raw data.


Because CBCharacteristic is an Obj-C class, and instances of classes are passed (in, say, function parameters) as references, not by copying their data. That's why such classes are called "reference types" — any variable of that type is actually a reference to the instance — to distinguish them from "value types", where a variable of that type contains the actual data.


Those terms are the most useful, and are what Swift uses. The traditional Obj-C terminology would be "class type" and "scalar type", since things that are passed around by value tend to be simple scalar values such as integers and doubles.


Finally, that's why variables referring to class instances in Obj-C have a "pointer-to" asterisk in their declaration (e.g. "CBCharacteristic*"), though the compiler knows that without you telling it. It just reinforces the fact that they are references. (In C++, just to confuse the issue, class instances can be created as value types or reference types.)

Thanks for the help! Took a while but appreciate it 🙂

bytes property on NSData
 
 
Q