8

I'm sorry if this is a bit of a C-noob question: I know I need to swot up on my pointers. Unfortunately I'm on a deadline so don't have time to work through a whole book chapter, so I'm hoping for a bit more targeted advice.

I want to store some objective-C objects in a C array. I'm using ARC. If I were on the Mac I'd be able to use NSPointerArray instead, but I'm on iOS and that's not available.

I'll be storing a three-dimensional C array: conceptually my dimensions are day, height, and cacheNumber. Each element will either be a pointer to an objective-C object, or NULL.

The number of caches (i.e. the size of the cacheNumber dimension) is known at compile time, but the other two are not known. Also, the array could be very large, so I need to dynamically allocate memory for it.

Regarding ownership semantics, I need strong references to the objects.

I would like the whole three-dimensional array to be an instance variable on an objective-C object.

I plan to have a method that is - tableForCacheNumber:(int)num days:(int*)days height:(int*)height. That method should return a two-dimensional array, that is one specific cache number. (It also passes back by reference the size of the array it is returning.)

My questions:

  • What order should I put my dimensions so that I can easily return a pointer to the subarray for one specific cache number? (I think it should be first, but I'm not 100%.)

  • What should the return type of my method be, so that ARC doesn't complain? I don't mind if the returned array has an increased reference count or not, as long as I know which it's doing.

  • What type should my instance variable that holds the three dimensional array be? I think it should just be a pointer, since that ivar just represents the pointer to the first item that's in my array. Correct? If so, how do I specify that?

  • When I create the three-dimensional array (for my ivar), I guess I do something like calloc(X * Y * Z, sizeof(id)), and cast the result to the type for my ivar?

  • When accessing items from the three-dimensional array in the ivar, I believe I have to dereference the pointer each time, with something like (*myArray)[4][7][2]. Correct?

  • Will the two-dimensional array I return from the method be similarly accessed?

  • Do I need to tag the returned two-dimensional array with objc_returns_inner_pointer?

I'm sorry once again that this is a bit of a bad Stack Overflow question (it's too long and with too many parts). I hope the SO citizens will forgive me. To improve my interweb karma, maybe I'll write it up as a blog post when this project has shipped.

1
  • Found an example on this page ("Can I create a C array of retained pointers under ARC?", near the bottom), which I'm attempting to extrapolate from. I'd still really appreciate clarification if anyone wishes to post an answer :) Commented Mar 26, 2012 at 10:08

3 Answers 3

5

First off: while you don't have NSPointerArray, you do have CFMutableArrayRef and you can pass any callbacks you want for retain/release/description, including NULL. It may be easier (and performance is something you can measure later) to try that first.

Taking your points in order:

  • you should define your dimensions as [cacheNumber][days][height], as you expect. Then cache[cacheNumber] is a two-dimensional array of type id *[][]. As you've said performance is important, be aware that the fastest way to iterate this beast is:

    for (/* cacheNumber loop */) {
     for (/* days loop */) {
      for (/* height loop */) {
       //...
      }
     }
    }
    
  • it should be of type __strong id ***: that's a pointer to a pointer to a pointer to id, which is the same as array of (array of (pointer to id)).

  • your ivar needs to be __strong id **** (!), because it's an array of the above things.
  • you guess incorrectly regarding allocating the array.. If you're using a multidimensional array, you need to do this (one dimension elided for brevity):

    - (__strong id * * *)someArray {
        __strong id * * *cache = (__strong id * * *)malloc(x*y*sizeof(void *));
        id hello = @"Hello";
    
        cache[0] = (__strong id * *)malloc(sizeof(void *)); //same for cache[1..x-1]
        cache[0][0] = &hello; // for all cache[x][y]
    
        return (__strong id * * *)cache;
    }
    
  • correct, that is how you use such a pointer.

  • yeah, the two-D array works in the same way, sans the first dimension.
  • I don't think so, you're handing out __strong object pointers so you should be grand. That said, we're at about the limit of my ability with this stuff now so I could well be wrong.
Sign up to request clarification or add additional context in comments.

Comments

3

Answering my own question because this web page gave me the missing bit of info I needed. I've also upvoted Graham's answer, since he was very helpful in getting my head round some of the syntax.

The trick I was missing is knowing that if I want to refer to items in the array via the array[1][5][2] syntax, and that I don't know the sizes of my array at compile time, I can't just calloc() a single block of data for it.

The easiest to read (although least efficient) method of doing that is just with a loop:

__strong Item ****cacheItems;

cacheItems = (__strong Item ****)calloc(kMaxZooms, sizeof(Item ***));

for (int k = 0; k < kMaxZooms; k++) 
{
    cacheItems[k] = (__strong Item ***)calloc((size_t)daysOnTimeline, sizeof(Item **));

    for (int j = 0; j < daysOnTimeline; j++) 
    {
        cacheItems[k][j] = (__strong Item **)calloc((size_t)kMaxHeight, sizeof(Item *));
    }
}

I'm allocating a three dimensional array of Item *s, Item being an objective-C class. (I have of course left out the error handling code in this snippet.)

Once I've done that, I can refer to my array using the square brackets syntax:

cacheItems[zoom][day][heightToUse] = item;

The web page I linked to above also describes a second method for performing the memory allocations, that uses only one call to calloc() per dimension. I haven't tried that method yet, as the one I've just described is working well enough at the moment.

Comments

0

I would think of a different implementation. Unless it is a demonstrable (i.e. you have measured and quantified it) performance issue, trying to store Objective-C objects in plain C arrays is often a code smell.

It seems to me that you need an intermediate container object which we will call a Cache for now. One instance will exist for each cache number, and your object will hold an NS(Mutable)Array of them. Cache objects will have properties for the maximum days and height.

The Cache object would most easily be implemented with an NSArray of the objects in it, using simple arithmetic to simulate two dimensions. Your cache object would have a method -objectAtDay:Height: to access the object by its coordinates.

This way, there is no need at all to worry about memory management, ARC does it for you.

Edit

Given that performance is an issue, I would use a 1D array and roll my own arithmetic to calculate offsets. The type of your instance variable would be:

__strong id* myArray;

You can only use C multilevel subscripts (array[i][j][k]) if you know the range of all the dimensions (except the first one). This is because the actual offset is calculated as

(i * (max_j * max_k) + j * max_k + k) * sizeof(element type)

If the compiler doesn't know max_j and max_k, it can't do it. That's precisely the situation you are in.

Given that you have to use a 1D array and calculate the offsets manually, the Apple example will work fine for you.

8 Comments

Oh, it's definitely a demonstrable performance issue. I've spent a number of weeks poured over Instruments trying to profile what's happening every frame in order to get the UI responsive. This is the fourth rewrite of the core logic in an effort to squeeze all the speed I can out of the device.
You might do better to cache a C array of the primitive property values you need to access fast than a C array of the objects.
@jeremyP: multidimensional subscripts rely on having arrays of arrays of arrays, not a large single-dimensional array with extra meaning ascribed to the indices. I don't think your sentence "You can only use…" is correct.
@Graham Lee: I am right. If you declare an array int myarray[5][5][5], the compiler will allocate one large block of memory and use arithmetic to index into it. It will not create an array of pointers to pointers to pointers. Arrays are not pointers in C contrary to popular myth.
@Graham: The questioner was looking to dynamically allocate one large chunk of RAM. I agree the compiler can't do what I said in my last comment, that is the point!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.