Макрос с actionBlock вызывает ошибки компилятора - PullRequest
0 голосов
/ 21 марта 2020

Я определил набор макросов, чтобы визуально упростить мой код при создании операторов UIActionController и UIAlertAction. Макросы работают нормально, но есть одна ситуация, которая вызывает ошибки компилятора. Хотя существует простой обходной путь, я пытаюсь понять, почему возникают эти ошибки и как переписать макрос, чтобы исключить необходимость обходного пути.

Ниже определены два макроса. Макрос first включен только для того, чтобы пример кода компилировался.

Моя проблема касается макроса second . У него есть два аргумента. Один - это строка, а другой - actionBlock (для completionHandler). Если actionBlock содержит оператор с массивом или вызов метода с несколькими аргументами (которые напоминают массив), компилятор жалуется. Ошибки можно устранить, добавив пару круглых скобок, как показано.

- (void)macroTest
{
#define ALERT_CREATE( title, msg )  \
    UIAlertController *alertController = \
        [UIAlertController alertControllerWithTitle:title \
        message:msg preferredStyle:UIAlertControllerStyleAlert]

#define ALERT_DEFAULT_ACTION( title, actionBlock ) \
    [alertController addAction:[UIAlertAction actionWithTitle:title \
        style:UIAlertActionStyleDefault \
        handler:^(UIAlertAction *action) { actionBlock } ]]

    // Example code
    ALERT_CREATE( @"Alert Title", @"Alert Message" );

    // This example is fine (no errors):
    ALERT_DEFAULT_ACTION( @"Action 1 Title" , /* actionBlock */
                         int i = 1;
                         i++;
                         );

    // This results in error: Use of undeclared identifier 'ALERT_DEFAULT_ACTION'
    ALERT_DEFAULT_ACTION( @"Action 2a Title" ,
                         // Another error: Too many arguments provided to function-like macro invocation
                         NSArray *test = @[ @"1", @"2" ]; // not okay
                         );

    ALERT_DEFAULT_ACTION( @"Action 2b Title" ,
                         // The same code as 2a, but with parens, is okay
                         NSArray *test = (@[ @"1", @"2" ]); // this is okay (with parens)
                         );

    ALERT_DEFAULT_ACTION( @"Action 3 Title" ,
                         // Parentheses are needed here, too, to avoid error
                         NSString *str1 = [NSString stringWithFormat:(@"format %@", @"arg")];                                   NSString *str2 = ([NSString stringWithFormat:@"format %@", @"arg"]);
                         );

    `enter code here`// The next line displays the alert.  It's commented out to avoid errors in this example.
    // [self presentViewController:alertController animated:YES completion:nil];
}

Почему компилятор жалуется и как я могу переписать второй макрос, чтобы мне не нужно было добавлять скобки в некоторые из заявления в его actionBlock? Я использую Xcode 11.3.

ОБНОВЛЕНИЕ: Это также смущает логику отступа Xcode c, если в actionBlock есть условие. Для остроумия:

ALERT_DEFAULT_ACTION( @"Action 4a Title" ,
                     // code here, correct indentation
                     );
ALERT_DEFAULT_ACTION( @"Action 4b Title" ,
                     if ( true ) {
    // code here, incorrect indentation
} // incorrect indenation
                     );

1 Ответ

0 голосов
/ 02 апреля 2020

Мой вопрос должен был быть помечен как дубликат, потому что есть несколько похожих вопросов с ответами. Хитрость заключается в использовании хороших условий поиска (подсказка, поиск: передача блока в макросе).

Решение состоит в том, чтобы использовать variadi c функции ("..." и va_args ). Последний аргумент макроса поглощает все в блоке, включая запятые, как объясняется в этой замечательной статье: Отправка блоков в качестве аргументов макросам https://mort.coffee/home/obscure-c-features/

В случае, если эта ссылка становится потерянной , вот важный момент:

Препроцессор поддерживает макросы variadi c; макросы, которые могут принимать неограниченное количество аргументов. Они реализованы так, что любые дополнительные аргументы (обозначенные ... в определении макроса) становятся доступны через идентификатор VA_ARGS ; VA_ARGS заменяется всеми дополнительными аргументами, разделенными запятыми. [Это передает] блок кода с неохраняемыми запятыми в макрос.

Этот пост отдает должное этому раннему решению 2012 года: { ссылка } Итак, +1 там.

Вот мой фиксированный макрос:

#define ALERT_DEFAULT_ACTION( title, ... /* actionBlock */ ) \
    [alertController addAction:[UIAlertAction actionWithTitle:title \
        style:UIAlertActionStyleDefault \
        handler:^(UIAlertAction *action) { {__VA_ARGS__} /* actionBlock */ }]]

Это не решает проблему некорректно заключенных в скобки (например, в операторах if). Это слишком запутанно для Xcode.

ALERT_DEFAULT_ACTION( @"title",
                     NSArray *test = @[ @"1", @"2" ]; // commas okay now
                     if ( true ) {
    test = test; // incorrect indentation
} // incorrect indentation
                     test = test;
                     );
...