Search code examples
objective-cparametersargumentsparameter-passing

How to pass a multidimensional C-array as NSTimer userInfo?


I need to call a function in Objective-C every x seconds, but this function needs to receive a 2D int as parameter.

- (void)myTimer:(int[2][3]) field {
   //Rest of the code
}

I tried to use the userInfo option to pass this array, but it looks like it only accepts NSObjects.

int Field[2][3];
memset(Field, 0x00, 6 * sizeof(int));
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(myTimer:) userInfo:Field repeats:YES];

//Error is: Implicit conversion of a non-Objective-C pointer type 'int (*)[3]' to 'id _Nullable' is disallowed with ARC

Is there a way to pass this 2D int or do I need to go with NSObjects or maybe a global variable?

Thank you


Solution

  • I don't think it's possible to pass a stack-allocated array to NSTimer (Objective-C is not very friendly to stack-allocated objects in general. You can pass a pointer to such an array, but NSTimer can outlive almost all stacks in the application, and you may end up with a dangling pointer then) so you at least need to make this array static or global.

    Then you can take a pointer to it, and wrap it with NSValue like this:

    static int data[array_rows][array_columns] = { { 0, 1, 2 }, { 3, 4, 5 } };
    NSValue *userInfo = [NSValue valueWithPointer:&data];
    

    Alternatively you can allocate the array in the dynamic memory:

    int (*array)[array_columns] = malloc(array_rows * array_columns * sizeof(int));
    if (array) {
        int val = 0;
        for (int i = 0; i < array_rows; ++i) {
            for (int j = 0; j < array_columns; ++j) {
                array[i][j] = val++;
            }
        }
    }
    NSValue *userInfo = [NSValue valueWithPointer:array];
    

    As you already noticed, NSTimer takes Objective-C classes as arguments, so NSValue can be passed as is:

    [NSTimer scheduledTimerWithTimeInterval:1
                                     target:self
                                   selector:@selector(timerAction:)
                                   userInfo:userInfo
                                    repeats:YES];
    

    Be advised that on the target side the array pointer looks somewhat different. For the static variable you need to dereference the pointer value:

    - (void)timerAction: (NSTimer *)timer {
        NSValue *userInfo = timer.userInfo;
        int (*array)[array_columns] = *(int (**)[array_columns])userInfo.pointerValue;
        ...
    

    And for the dynamic memory array it's already dereferenced:

    - (void)timerAction: (NSTimer *)timer {
        NSValue *userInfo = timer.userInfo;
        int (*array)[array_columns] = userInfo.pointerValue;
        ...