Objective-C ARC and passing C arrays of objects

Sorry if this is a bit of a C-noob question: I know that I need to stroke my pointers. Unfortunately, I am in a deadline, so I don’t have time to work on a complete book chapter, so I hope for more focused advice.

I want to store some objective-C objects in an array C. I use ARC. If I were on a Mac, I could use NSPointerArray, but I'm on iOS and not available.

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

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

Regarding ownership semantics, I need strong object references.

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

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

My questions:

  • In what order should my measurements be placed so that I can easily return a pointer to a subarray for one specific cache number? (I think this should be the first, but I'm not 100%.)

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

  • What type should an instance variable that contains a three-dimensional array have? I think this should be just a pointer, since this ivar just represents a pointer to the first element that is in my array. Right? If so, how can I indicate this?

  • When I create a three-dimensional array (for my ivar), I think I'm doing something like calloc(X * Y * Z, sizeof(id)) , and pass the result to the type for my ivar?

  • When accessing elements from a three-dimensional array in ivar, I believe that every time you need to play a pointer with something like (*myArray)[4][7][2] . Correctly?

  • Will there be similar access to the two-dimensional array returned by my method?

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

I regret again that this is a bit of a bad question (it is too long and with too many parts). I hope EU citizens will forgive me. To improve my interweb karma, I may write it as a blog post when this project is submitted.

+8
ios objective-c automatic-ref-counting
source share
3 answers

Answering my own question, because this webpage gave me the missing bit of information that I needed. I also supported Graham's answer, as it really helped me understand the syntax.

I missed the trick, knowing that if I want to reference the elements of an array using the syntax array[1][5][2] and that I do not know the size of my array at compile time, I cannot just calloc() for it alone data block.

The easiest to read (though least effective) way to do this is simply 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 allocate a three-dimensional array of Item * s, Item as an objective-C class. (In this snippet, of course, I did not take into account the error handling code.)

Once I have done this, I can reference my array using square bracket syntax:

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

The webpage I linked to above also describes a second way of doing memory allocations, which uses only one calloc() call for each dimension. I have not tried this method yet, since the one I just described works quite well at the moment.

+3
source share

First of all: as long as you don't have NSPointerArray , you have CFMutableArrayRef , and you can pass any callbacks that you want to save / release / description, including NULL . It might be easier (and performance is something you can measure later) to try it first.

Taking your points in order:

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

     for (/* cacheNumber loop */) { for (/* days loop */) { for (/* height loop */) { //... } } } 
  • it must be of type __strong id *** : this is a pointer to a pointer to a pointer to an id that matches the array (array (pointer to id )).

  • your ivar should be __strong id **** (!), because it is an array of the above things.
  • you incorrectly assume array allocation. If you are using a multidimensional array, you need to do this (one dimension has been reduced 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; } 
  • That's right, here's how you use such a pointer.

  • Yes, a two-dimensional array works the same, does not have a first dimension.
  • I don’t think so, you pass __strong object __strong so that you are great. Nevertheless, we are now at the limit of our capabilities with this material, so I could have been mistaken.
+5
source share

I would think of another implementation. If this is not proven (i.e. you measured and quantified it), a performance problem, trying to store Objective-C objects in simple C arrays is often a smell of code.

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

The Cache object is easiest to implement using NSArray objects in it, using simple arithmetic to model two dimensions. The cache object will have the -objectAtDay:Height: method to access the object by its coordinates.

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

Edit

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

 __strong id* myArray; 

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

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

If the compiler does not know max_j and max_k, he cannot do this. This is exactly the situation you are in.

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

0
source share

All Articles