Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

There's got to be an easier way of dealing with arrays!

To optimize a bottleneck, I converted the creation of a large NSArray to a c-style array. (The resulting creation was 1/8 the time of the original NSArray version. Yeah!) But once it's created, speed is no longer an issue, so I'd rather benefit from it being an NSArray again.

However, it seems ridiculously involved to convert a c-style array to an NSArray (unless I'm missing some magic initWithArrayWrapElementsInObjects method.)

As I understand the process now, I first have to create an NSMutableArray, iterate through the c-style array converting each element (floats in my case) to objects, adding each object to the NSMutableArray, then creating the NSArray with the NSMutableArray.

Is that right? There's got to be a better way.

And help would be appreciated.

Thanks!

like image 539
Eric Christensen Avatar asked Jan 03 '10 16:01

Eric Christensen


1 Answers

There's no direct way to take a blob of memory that you own and "convert" it cheaply into an NSArray-- after all, the framework would need to then own that memory, and it doesn't know where you got it from (malloc, stack, etc). If there were a convenience method for initWithArrayWrapElementsInObjects, it would itself need to do internally what you surmise: iterate over your provided memory and add items to itself (it's possibly the framework could do this as quickly as a memcpy, but who knows).

One way you could tackle this (and probably a fun learning exercise) is by actually creating your own subclass of NSArray that manages memory exactly as you want (ie, lets you create and init with whatever semantics you want), but that behaves to the outside world as an NSArray would. You can do this by inheriting from NSArray and implementing the methods count: and objectAtIndex: to operate on whatever memory you're holding on to. Obviously, you'd need to implement the management of your own memory in the init/dealloc, etc methods as well. See this page http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html under "Subclassing Notes".

The design discussion here hinges on what your data looks like. NSArray, of course, expects its items to be Obj-C references (of type id), and not just arbitrary chunks of data. If your C-style array is holding structures or some other primitive values that aren't object references, then this technique won't really work for you-- NSArray's interface will never be happy with non-reference items.

One final note: you mention taking an NSMutableArray and "creating" an NSArray with it. You should be aware that an NSMutableArray is already an NSArray, since it's a subclass. You can use an instance of NSMutableArray anywhere you'd want an NSArray, without creating some new copy of it.

UPDATE: Missed the note about your array containing floats. Yeah, you're a little bit screwed here. NSArrays want objects. If the capacity doubling was the expensive part (as another poster notes), then try initWithCapacity:. If it's the boxing/unboxing of the floats into object types, there's nothing you can do.

I have created (but don't have handy) a pair of very simple classes (called like MYArray and MYMutableArray) that are intended to wrap just this kind of data with NSArray-like methods on them. But they're not interchangeable with NSArrays. You must use them intentionally.

UPDATE #2. I know it's been ages since this question was live, but I just revisited it and realized there actually is a sort of clever way around this in this specific case. (You want a non-mutable NSArray from a C-style float array). You can create a custom subclass of NSArray that wraps the float values and only converts them to objects when they're accessed via the primitives. This may have performance pitfalls in some corners (?), but it does neatly meet your requirements:

@interface FloatProxyArray : NSArray
{
    float * values;
    NSUInteger count;
}
- (id)initWithCArray:(float *)arrayOfFloats count:(int)numberOfValues;
@end

.

@implementation FloatProxyArray
- (id)initWithCArray:(float *)arrayOfFloats count:(int)numberOfValues
{
    if ((self = [super init])) {
        values = (float *)malloc(numberOfValues * sizeof(float));
        if (!values) { 
            [self release]; return nil; 
        }
        memcpy(values, arrayOfFloats, numberOfValues * sizeof(float));
        count = numberOfValues;
    }
    return self;
}

- (void)dealloc
{
    free(values);
    [super dealloc]
}

- (NSUInteger)count
{
    return count;
}

- (id)objectAtIndex:(NSUInteger)index
{
    if (index >= count) {
        [NSException raise:NSRangeException format:@""];
        return nil;
    }

    float val = values[index];
    return [NSNumber numberWithFloat:val];
}
@end

(N.B. Written in the editor without compiling/testing.)

like image 121
Ben Zotto Avatar answered Nov 15 '22 07:11

Ben Zotto