I'm coming from Swift to Objective-C and a problem I have run into is that NSData doesn't seem have methods to enumerate over UInt8 values the way Swift's Data does. Other answers with similar titles exist but they all deal with property lists, whereas I just have a bucket of ASCII bytes. Specifically, the code I want to replicate in Obj-C is:
//Find the next newline in ASCII data given a byte offset
let subset = myData.advanced(by: offset)
var lineEnd: Data.Index = myData.endIndex
subset.enumerateBytes({ (memory, idx, stop) in
let newline: UInt8 = 10
for idx in memory.indices {
let charByte = memory[idx]
if charByte == newline {
lineEnd = idx
stop = true; return;
}
}
})
Ideally I want a way to convert an NSArray to an array of NSNumbers which I can extract the intValues from. Of course, if there is a better method, let me know. A goal is to keep the Obj-C code as similar to Swift as possible as I will be maintaining the two codebases simultaneously.
The only good way to access an NSData's bytes is to call its -bytes method, which gets you a C pointer to its internal storage.
NSData *data = ...;
const uint8_t *bytes = data.bytes;
NSUInteger length = data.length;
for (NSUInteger i = 0; i < length; i++) {
uint8_t byte = bytes[i];
// do something with byte
}
The closest equivalent to advance would be subdataWithRange. The equivalent to enumerateBytes is enumerateByteRangesUsingBlock. It would yield something like:
NSData *subset = [data subdataWithRange:NSMakeRange(offset, data.length - offset)];
__block NSUInteger lineEnd = data.length;
Byte newline = 10;
[subset enumerateByteRangesUsingBlock:^(const void * _Nonnull bytes, NSRange byteRange, BOOL * _Nonnull stop) {
for (NSInteger index = 0; index < byteRange.length; index++) {
Byte charByte = ((Byte *)bytes)[index];
if (charByte == newline) {
lineEnd = index + byteRange.location;
*stop = true;
return;
}
}
}];
Note, I made a few changes from your Swift example:
If the data was not contiguous, your Swift example returns the index within the current block. But I suspect you want the location within subset, not the current block. I'm wagering that you've never noticed this because it's pretty rare that NSData blocks are not continuous.
But the Swift code doesn't look correct to me. This Objective-C example reports the offset within subset, not within the current block within subset.
It's not observable performance difference, but I pulled the definition of newline out of the enumeration block. Why repeatedly define that?
If you're really searching for a character in the NSData, I'd suggest avoiding creating the subset altogether. Just use rangeOfData:options:range:. This will find whatever you're looking for.
Related
The method enumerateByteRangesUsingBlock: is in class NSData, and interpreted in Apple Documentation as following:
Enumerate through each range of bytes in the data object using a block.
- (void)enumerateByteRangesUsingBlock:(void (^)(const void *bytes, NSRange byteRange, BOOL *stop))block
Parameters
block
The block to apply to byte ranges in the array.
The block takes three arguments:
bytes
The bytes for the current range.
byteRange
The range of the current data bytes.
stop
A reference to a Boolean value. The block can set the value to YES to stop further processing of the data. The stop argument is an out-only argument. You should only ever set this Boolean to YES within the Block.
Discussion
The enumeration block is called once for each contiguous region of memory in the receiver (once total for a contiguous NSData object), until either all bytes have been enumerated, or the stop parameter is set to YES.
But my question is, when will the block be executed? Which method is responsible for providing arguments bytes, byteRange and stop for the block? For example, if I want to traverse a part of the bytes array, what should I do to control?
The bytes, byteRange and stop parameters are passed to your block by enumerateByteRangesUsingBlock. You don't specify which bytes you want to traverse - you use this method to traverse all of the bytes (You can terminate early via stop).
As a simple example, say you wanted to search through some NSData looking for a 0xff. You could use -
NSInteger ffFound=NSNotFound;
[myData enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
for (NSInteger i=0;i<byteRange.length;i++) {
if (bytes[i]== 0xff) {
ffFound=byteRange.location+i;
*stop=YES;
break;
}
}
}];
if (ffFound != NSNotFound) {
NSLog(#"0xFF was found at location %ld",(long)ffFound);
}
The other answers are good, but didn't answer this part:
when will the block be executed?
The block will be executed immediately, one or more times, before -enumerateByteRangesUsingBlock: returns.
You may be confused because many methods which take a block are asynchronous. They return but retain the block and continue to do some work (or monitor some events or input) in the background and call the block later.
-enumerateByteRangesUsingBlock: is not like that. It's synchronous. It does all of its work before returning to the caller.
[NSData enumerateByteRangesUsingBlock:] is used for reading contiguous region of memory of fetched data which means that as the iterator encounters a new chunk, it will execute the block passed to it.
In addition to that, it is not guaranteed that the block will be called more than once (in other words, data may not be fragmented) and the size of bytes passed to block will always be same.
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://httpbin.org/image/png"]];
[data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
NSLog(#"You get the chunk in range: %#", NSStringFromRange(byteRange));
}];
Alternatively, you can also enumerate over the data and read fixed size of chunk at each iteration;
NSUInteger length = [data length];
NSUInteger chunkSize = 1024;
NSUInteger chunkOffset = 0;
do {
NSUInteger chunkSize = MIN(length - chunkOffset, chunkSize);
NSData *chunk = [data subdataWithRange:NSMakeRange(chunkOffset, chunkSize)];
chunkOffset = chunkOffset + chunkSize;
} while (chunkOffset < length);
I am making a program where I need to loop through an array with a list of letters. I want the letters to be shown on their specific label. I have therefore created an outlet of each (about 38) and given them the name "alif01", "alif02", etc.
for (int i = 0; i < [arabicLetters count]; i++) {
int num = i;
NSString *letterString = [arabicLetters objectAtIndex:i];
NSLog(#"alif0%d is %#", num, letterString);
alif0**[i]**.text = arabicLetters[i];
}
is it possible to use the index [i] instead of writing it all manually?
You should not have 38 IBOutlet properties for this. You should have an array (possibly an IBOutletCollection) so that you can loop over the array / index into the array.
While technically you can create a key name and use KVC valueForKey: (appending strings / string format), the array approach is a much better solution.
Indeed, as you already have a loop, you would be better served by creating the labels in the loop directly, then you know you have the correct number. This is particularly beneficial later, when you change the contents of arabicLetters (though that sounds like it isn't a concern in this particular case).
Try with below code:
for (int i = 0; i < [arabicLetters count]; i++) {
NSString *letterString = [arabicLetters objectAtIndex:i];
NSString *propertyName = [NSString stringWithFormat:#"alif0%d.text",i];
[self setValue:letterString forKeyPath:propertyName];
}
I'm using the following code to obfuscate a passcode for a test app of mine.
- (NSString *)obfuscate:(NSString *)string withKey:(NSString *)key
{
// Create data object from the string
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
// Get pointer to data to obfuscate
char *dataPtr = (char *) [data bytes];
// Get pointer to key data
char *keyData = (char *) [[key dataUsingEncoding:NSUTF8StringEncoding] bytes];
// Points to each char in sequence in the key
char *keyPtr = keyData;
int keyIndex = 0;
// For each character in data, xor with current value in key
for (int x = 0; x < [data length]; x++)
{
// Replace current character in data with
// current character xor'd with current key value.
// Bump each pointer to the next character
*dataPtr = *dataPtr++ ^ *keyPtr++;
// If at end of key data, reset count and
// set key pointer back to start of key value
if (++keyIndex == [key length])
keyIndex = 0, keyPtr = keyData;
}
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
This works like a charm with all strings, but i've ran into a bit of a problem comparing the following results
NSLog([[self obfuscate:#"0000", #"maki"]); //Returns 0]<W
NSLog([[self obfuscate:#"0809", #"maki"]); //Returns 0]<W
As you can see, the two strings with numbers in, while different, return the same result! Whats gone wrong in the code i've attached to result in the same result for these two numbers?
Another example:
NSLog([self obfuscate:#"8000" withKey:#"maki"]); //Returns 8U4_
NSLog([self obfuscate:#"8290" withKey:#"maki"]); //Returns 8U4_ as well
I may be misunderstanding the concept of obfuscation, but I was under the impression that each unique string returns a unique obfuscated string!
Please help me fix this bug/glitch
Source of Code: http://iosdevelopertips.com/cocoa/obfuscation-encryption-of-string-nsstring.html
The problem is your last line. You create the new string with the original, unmodified data object.
You need to create a new NSData object from the modified dataPtr bytes.
NSData *newData = [NSData dataWithBytes:dataPtr length:data.length];
return [[NSString alloc] initWithData:newData encoding:NSUTF8StringEncoding];
But you have some bigger issues.
The calls to bytes returns a constant, read-only reference to the bytes in the NSData object. You should NOT be modifying that data.
The result of your XOR on the character data could, in theory, result in a byte stream that is no longer a valid UTF-8 encoded string.
The obfuscation algorithm that you have selected is based on XORing the data and the "key" values together. Generally, this is not very strong. Moreover, since XOR is symmetric, the results are very prone to producing duplicates.
Although your implementation is currently broken, fixing it would not be of much help in preventing the algorithm from producing identical results for different data: it is relatively straightforward to construct key/data pairs that produce the same obfuscated string - for example,
[self obfuscate:#"0123" withKey:#"vwxy"]
[self obfuscate:#"pqrs" withKey:#"6789"]
will produce identical results "FFJJ", even though both the strings and the keys look sufficiently different.
If you would like to "obfuscate" your strings in a cryptographically strong way, use a salted secure hash algorithm: it will produce very different results for even slightly different strings.
I have limited experience working with the Core Foundation types & collections, so apologies if this is obvious.
I'm using the CFBitVector type to store some bit sequences, and I need to store it in a binary data format (so that it can be added to a Core Data store). The most sensible thing seems to be to store this in a CFData type, which can be toll-free bridged with an NSData and added to the store, but I am unsure of how to do this.
Can anybody help me out with a simple example of storing CFTypes in CF/NSData?
Edit:
Is this even the right approach? Should I try converting the CFBitVector into a series of ints which can then be stored in the data model? Or perhaps a transformable attribute?
The way I ended up doing this was to roll my own attribute transformer in order to convert a CFBitVectorRef into an NSData instance. The benefit of this is that I can really cram the bit array tightly into a block of binary data, as in my case I really need to keep the storage size to a minimum.
Below is the implementation of my CFBitVectorTransformer class. It essentially reads each bit and packs them into unsigned chars ("segments" in the code below), which are then appended to a mutable NSData buffer. The code would work with types larger than unsigned chars, however I wanted the smallest chunks possible in order to really minimise the size of the resulting data.
#define kBitsPerByte 8
#implementation CFBitVectorTransformer
+ (Class)transformedValueClass
{
return [NSData class];
}
+ (BOOL)allowsReverseTransformation
{
return YES;
}
/* CFBitVectorRef -> NSData */
- (id)transformedValue:(id)value
{
if (!value) return nil;
if ([value isKindOfClass:[NSData class]]) return value;
/* Prepare the bit vector. */
CFBitVectorRef bitVector = (__bridge CFBitVectorRef)value;
CFIndex bitVectorCount = CFBitVectorGetCount(bitVector);
/* Prepare the data buffer. */
NSMutableData *bitData = [NSMutableData data];
unsigned char bitVectorSegment = 0;
NSUInteger bytesPerSegment = sizeof(char);
NSUInteger bitsPerSegment = bytesPerSegment * kBitsPerByte;
for (CFIndex bitIndex = 0; bitIndex < bitVectorCount; bitIndex++) {
/* Shift the bit into the segment the appropriate number of places. */
CFBit bit = CFBitVectorGetBitAtIndex(bitVector, bitIndex);
int segmentShift = bitIndex % bitsPerSegment;
bitVectorSegment |= bit << segmentShift;
/* If this is the last bit we can squeeze into the segment, or it's the final bit, append the segment to the data buffer. */
if (segmentShift == bitsPerSegment - 1 || bitIndex == bitVectorCount - 1) {
[bitData appendBytes:&bitVectorSegment length:bytesPerSegment];
bitVectorSegment = 0;
}
}
return [NSData dataWithData:bitData];
}
/* NSData -> CFBitVectorRef */
- (id)reverseTransformedValue:(id)value
{
if (!value) return NULL;
if (![value isKindOfClass:[NSData class]]) return NULL;
/* Prepare the data buffer. */
NSData *bitData = (NSData *)value;
char *bitVectorSegments = (char *)[bitData bytes];
NSUInteger bitDataLength = [bitData length];
/* Prepare the bit vector. */
CFIndex bitVectorCapacity = bitDataLength * kBitsPerByte;
CFMutableBitVectorRef bitVector = CFBitVectorCreateMutable(kCFAllocatorDefault, bitVectorCapacity);
CFBitVectorSetCount(bitVector, bitVectorCapacity);
for (NSUInteger byteIndex = 0; byteIndex < bitDataLength; byteIndex++) {
unsigned char bitVectorSegment = bitVectorSegments[byteIndex];
/* Store each bit of this byte in the bit vector. */
for (NSUInteger bitIndex = 0; bitIndex < kBitsPerByte; bitIndex++) {
CFBit bit = bitVectorSegment & 1 << bitIndex;
CFIndex bitVectorBitIndex = (byteIndex * kBitsPerByte) + bitIndex;
CFBitVectorSetBitAtIndex(bitVector, bitVectorBitIndex, bit);
}
}
return (__bridge_transfer id)bitVector;
}
#end
This nicely abstracts the conversion of data, allowing you to just set the CFBitVectorRef as an attribute in the data model, and should be plenty fast enough for most purposes.
I hope this helps somebody else in a similar situation.
I have a binary file I've loaded using an NSData object. Is there a way to locate a sequence of characters, 'abcd' for example, within that binary data and return the offset without converting the entire file to a string? Seems like it should be a simple answer, but I'm not sure how to do it. Any ideas?
I'm doing this on iOS 3 so I don't have -rangeOfData:options:range: available.
I'm going to award this one to Sixteen Otto for suggesting strstr. I went and found the source code for the C function strstr and rewrote it to work on a fixed length Byte array--which incidentally is different from a char array as it is not null terminated. Here is the code I ended up with:
- (Byte*)offsetOfBytes:(Byte*)bytes inBuffer:(const Byte*)buffer ofLength:(int)len;
{
Byte *cp = bytes;
Byte *s1, *s2;
if ( !*buffer )
return bytes;
int i = 0;
for (i=0; i < len; ++i)
{
s1 = cp;
s2 = (Byte*)buffer;
while ( *s1 && *s2 && !(*s1-*s2) )
s1++, s2++;
if (!*s2)
return cp;
cp++;
}
return NULL;
}
This returns a pointer to the first occurrence of bytes, the thing I'm looking for, in buffer, the byte array that should contain bytes.
I call it like this:
// data is the NSData object
const Byte *bytes = [data bytes];
Byte* index = [self offsetOfBytes:tag inBuffer:bytes ofLength:[data length]];
Convert your substring to an NSData object, and search for those bytes in the larger NSData using rangeOfData:options:range:. Make sure that the string encodings match!
On iPhone, where that isn't available, you may have to do this yourself. The C function strstr() will give you a pointer to the first occurrence of a pattern within the buffer (as long as neither contain nulls!), but not the index. Here's a function that should do the job (but no promises, since I haven't tried actually running it...):
- (NSUInteger)indexOfData:(NSData*)needle inData:(NSData*)haystack
{
const void* needleBytes = [needle bytes];
const void* haystackBytes = [haystack bytes];
// walk the length of the buffer, looking for a byte that matches the start
// of the pattern; we can skip (|needle|-1) bytes at the end, since we can't
// have a match that's shorter than needle itself
for (NSUInteger i=0; i < [haystack length]-[needle length]+1; i++)
{
// walk needle's bytes while they still match the bytes of haystack
// starting at i; if we walk off the end of needle, we found a match
NSUInteger j=0;
while (j < [needle length] && needleBytes[j] == haystackBytes[i+j])
{
j++;
}
if (j == [needle length])
{
return i;
}
}
return NSNotFound;
}
This runs in something like O(nm), where n is the buffer length, and m is the size of the substring. It's written to work with NSData for two reasons: 1) that's what you seem to have in hand, and 2) those objects already encapsulate both the actual bytes, and the length of the buffer.
If you're using Snow Leopard, a convenient way is the new -rangeOfData:options:range: method in NSData that returns the range of the first occurrence of a piece of data. Otherwise, you can access the NSData's contents yourself using its -bytes method to perform your own search.
I had the same problem.
I solved it doing the other way round, compared to the suggestions.
first, I reformat the data (assume your NSData is stored in var rawFile) with:
NSString *ascii = [[NSString alloc] initWithData:rawFile encoding:NSAsciiStringEncoding];
Now, you can easily do string searches like 'abcd' or whatever you want using the NSScanner class and passing the ascii string to the scanner. Maybe this is not really efficient, but it works until the -rangeOfData method will be available for iPhone also.