19

Blocks are fine but what about writing C arrays?

Given this simplified situation:

CGPoint points[10];
[myArray forEachElementWithBlock:^(int idx) {
    points[idx] = CGPointMake(10, 20); // error here
    // Cannot refer to declaration with an array type inside block
}];

after searching a while found this possible solution, to put it in a struct:

__block struct {
    CGPoint points[100];
} pointStruct;

[myArray forEachElementWithBlock:^(int idx) {
    pointStruct.points[idx] = CGPointMake(10, 20);
}];

this would work but there is a little limitation I have to create the c array dynamically:

int count = [str countOccurencesOfString:@";"];
__block struct {
    CGPoint points[count]; // error here
    // Fields must have a constant size: 'variable length array in structure' extension will never be supported
} pointStruct;

How can I access my CGPoint array within a block?

OR

Is it even possible at all or do I have to rewrite the block method to get the full functionality?

2 Answers2

20

Another simple answer which works for me is the following:

CGPoint points[10], *pointsPtr;
pointsPtr = points;
[myArray forEachElementWithBlock:^(int idx) {
    pointsPtr[idx] = CGPointMake(10, 20);
}];
cefstat
  • 2,336
  • 1
  • 18
  • 25
  • 1
    I'm unfamiliar with the `, *pointsPtr` and association. It creates a pointer but should I release anything at the end? –  Jun 22 '11 at 13:14
  • 2
    You don't have to release anything. The pointer `pointsPtr` is a normal variable and its value is (after `pointsPtr = points`) the memory address of the first element of the array `points`. So when you declare `points[10]` the compiler reserves memory on the stack for 10 `CGPoint`'s and then `pointsPtr` just points to this memory. Since you don't allocate any memory for `pointsPtr` on the heap with `malloc`/`calloc` there is nothing to release. This is why I prefer this method. BTW, I could have started my code with `CGPoints points[10]; CGPoint *pointsPtr = points;` which is exactly the same. – cefstat Jun 23 '11 at 09:23
  • 9
    It is true that in this case the block is only used in this scope. However, in general, blocks can be copied and returned from the function and be used later when these variables no longer exist. In that case, this solution won't work and will overwrite arbitrary memory – user102008 Nov 14 '11 at 02:45
  • This is unsafe, but it could be used if you 100% sure that you won't you reach this block outside the function/method's scope. In general, such hacks are continuously laying tons of technical obligations on your shoulders, so they must be well-commented at least. – Netherwire Feb 06 '19 at 21:56
14

Maybe you can allocate the array on the heap?

// Allocates a plain C array on the heap. The array will have
// [myArray count] items, each sized to fit a CGPoint.
CGPoint *points = calloc([myArray count], sizeof(CGPoint));
// Make sure the allocation succeded, you might want to insert
// some more graceful error handling here.
NSParameterAssert(points != NULL);

// Loop over myArray, doing whatever you want
[myArray forEachElementWithBlock:^(int idx) {
    points[idx] = …;
}];

// Free the memory taken by the C array. Of course you might
// want to do something else with the array, otherwise this
// excercise does not make much sense :)
free(points), points = NULL;
zoul
  • 102,279
  • 44
  • 260
  • 354
  • Could you use a `dispatch_sync` call with *someQueue* after this to delete the array at the end instead? – James Bedford Mar 28 '11 at 07:16
  • I think I don't have to use GCD because its a linear synchronous operation so I can simply delete it after my block call, or? Could you please explain what free() does and how should I delete the array afterwards? –  Mar 28 '11 at 07:38
  • Sorry, the `dispatch_async` was just an example, obviously not the best one. I’ll rewrite the answer to be closer to your actual code. – zoul Mar 28 '11 at 07:43
  • 1
    How does this work given that by the time the code in the block gets executed, isn't it possible that points has been freed? Does GCD make a copy of points when you declare the block? – James Bedford Mar 28 '11 at 07:51
  • Blocks != asynchronous != GCD. I don’t know where exactly does `forEachElementWithBlock` come from, but judging by the interface and iPortable’s comments it’s synchronous. Therefore the array is only freed after the array iteration finishes and everything is fine. – zoul Mar 28 '11 at 07:56
  • `forEachElementWithBlock` was only an example I'm currently using a `NSString` category `componentsByDelimiter:usingBlock:` but this wouldn't make sense either (or wouldn't affect the basic question). But thanks for this excellent answer :) –  Mar 28 '11 at 08:03
  • 1
    It is true that in this case the block is only used in this scope. However, in general, blocks can be copied and returned from the function and be used later when these variables no longer exist. In that case, this solution won't work and will overwrite arbitrary memory – user102008 Nov 14 '11 at 02:44
  • @zoul - this is not I don't understand. On the first like you say you are allocating a C array called `points` on the heap but when you iterate you use `myArray`... (?) ..... sorry I am not good on C – Duck Jul 27 '17 at 14:37