Подтвердить что ты не робот

Почему мигратор ARC говорит, что NSInvocation -setArgument: небезопасно, если аргумент отсутствует __unsafe_unretained?

Я переносил блок кода в автоматический подсчет ссылок (ARC), и когда ARC-мигратор выдавал ошибку

NSInvocation setArgument небезопасно для использования с объектом с собственность, кроме __unsafe_unretained

в коде, где я выделил объект, используя что-то вроде

NSDecimalNumber *testNumber1 = [[NSDecimalNumber alloc] initWithString:@"1.0"];

затем установите его как аргумент NSInvocation, используя

[theInvocation setArgument:&testNumber1 atIndex:2];

Почему это мешает вам это делать? Похоже, так же плохо использовать объекты __unsafe_unretained в качестве аргументов. Например, следующий код вызывает сбой при ARC:

NSDecimalNumber *testNumber1 = [[NSDecimalNumber alloc] initWithString:@"1.0"];
NSMutableArray *testArray = [[NSMutableArray alloc] init];

__unsafe_unretained NSDecimalNumber *tempNumber = testNumber1;

NSLog(@"Array count before invocation: %ld", [testArray count]);
//    [testArray addObject:testNumber1];    
SEL theSelector = @selector(addObject:);
NSMethodSignature *sig = [testArray methodSignatureForSelector:theSelector];
NSInvocation *theInvocation = [NSInvocation invocationWithMethodSignature:sig];
[theInvocation setTarget:testArray];
[theInvocation setSelector:theSelector];
[theInvocation setArgument:&tempNumber atIndex:2];
//        [theInvocation retainArguments];

// Let say we don't use this invocation until after the original pointer is gone
testNumber1 = nil;

[theInvocation invoke];
theInvocation = nil;

NSLog(@"Array count after invocation: %ld", [testArray count]);
testArray = nil;

из-за переопределения testNumber1, поскольку временная переменная __unsafe_unretained tempNumber не удерживается на ней после того, как исходный указатель установлен на nil (имитируя случай, когда вызов используется после оригинала ссылка на аргумент ушла). Если строка -retainArguments раскоментирована (заставляя NSInvocation удерживать аргумент), этот код не сбой.

То же самое происходит, если я использую testNumber1 непосредственно в качестве аргумента для -setArgument:, и он также исправляется, если вы используете -retainArguments. Почему же мигратор ARC говорит, что использование строго удерживаемого указателя в качестве аргумента для NSInvocation -setArgument: небезопасно, если вы не используете что-то, что есть __unsafe_unretained?

4b9b3361

Ответ 1

Это полное предположение, но может быть, это связано с тем, что аргумент передается по ссылке как void*?

В случае, о котором вы упомянули, это действительно не проблема, но если вы должны позвонить, например. getArgument:atIndex:, тогда компилятор не имел бы никакого способа узнать, нужно ли сохранить возвращаемый аргумент.

Из NSInvocation.h:

- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;

Учитывая, что компилятор не знает, будет ли метод возвращаться по ссылке или нет (эти два объявления метода имеют одинаковые типы и атрибуты), возможно, мигрент (разумно) осторожен и говорит вам избегать указателей void для сильных указатели?

Например:

NSDecimalNumber* val;
[anInvocation getArgument:&val atIndex:2];
anInvocation = nil;
NSLog(@"%@", val); // kaboom!


__unsafe_unretained NSDecimalNumber* tempVal;
[anInvocation getArgument:&tempVal atIndex:2];
NSDecimalNumber* val = tempVal;
anInvocation = nil;
NSLog(@"%@", val); // fine

Ответ 2

An NSInvocation по умолчанию не сохраняет или не копирует заданные аргументы для эффективности, поэтому каждый объект, переданный как аргумент, должен оставаться в живых при вызове вызова. Это означает, что указатели, переданные в -setArgument:atIndex:, обрабатываются как __unsafe_unretained.

Две строки кода MRR, которые вы опубликовали, ушли с этим: testNumber1 никогда не выпускался. Это привело бы к утечке памяти, но сработало бы. Однако в ARC testNumber1 будет выпущен где угодно между его последним использованием и концом блока, в котором он определен, поэтому он будет освобожден. Перейдя на ARC, код может упасть, поэтому средство миграции ARC не позволяет вам выполнить миграцию:

NSInvocation setArgument небезопасно для использования с объектом с собственность, кроме __unsafe_unretained

Просто передавая указатель, поскольку __unsafe_unretained не устранит проблему, вы должны убедиться, что аргумент все еще существует, когда вызов вызывается. Один из способов сделать это - вызов -retainArguments, как и вы (или даже лучше: сразу после создания NSInvocation). Затем вызов сохраняет все свои аргументы, и поэтому он сохраняет все необходимое для вызова. Это может быть не так эффективно, но это определенно предпочтительнее крушения;)

Ответ 3

Почему это мешает вам это делать? Похоже, что использовать __unsafe_unretained объекты в качестве аргументов также не так.

Сообщение об ошибке может быть улучшено, но мигратор не говорит, что объекты __unsafe_unretained безопасны для использования с NSInvocation (там нет ничего безопасного с __unsafe_unretained, оно есть в имени). Цель ошибки - обратить ваше внимание на то, что передача сильных/слабых объектов в этот API небезопасна, ваш код может взорваться во время выполнения, и вы должны проверить код, чтобы убедиться, что он не будет.

Используя __unsafe_unretained, вы в основном вводите явные небезопасные точки в свой код, где вы берете контроль и ответственность за то, что происходит. Хорошая гигиена делает эти небезопасные точки видимыми в коде при работе с NSInvocation, а не под иллюзией, что ARC будет правильно обрабатывать вещи с помощью этого API.

Ответ 4

Бросьте в мою полную догадку здесь.

Вероятно, это напрямую связано с retainArguments, существующим вообще при вызове. В общем, все методы описывают, как они будут обрабатывать любые переданные им аргументы с аннотациями непосредственно в параметре. Это не может работать в случае NSInvocation, потому что среда выполнения не знает, что вызовет будет делать с параметром. Цель ARC - сделать все возможное, чтобы гарантировать отсутствие утечек, без этих аннотаций на программиста проверить, нет ли утечки. Заставляя вас использовать __unsafe_unretained, заставляя вас это делать.

Я бы сделал это до одного из причуд с ARC (другие включают некоторые вещи, не поддерживающие слабые ссылки).

Ответ 5

Важным здесь является стандартное поведение NSInvocation: По умолчанию аргументы не сохраняются, а аргументы строки C не копируются. Поэтому в ARC ваш код может вести себя следующим образом:

// Creating the testNumber
NSDecimalNumber *testNumber1 = [[NSDecimalNumber alloc] initWithString:@"1.0"];

// Set the number as argument
[theInvocation setArgument:&testNumber1 atIndex:2];

// At this point ARC can/will deallocate testNumber1, 
// since NSInvocation does not retain the argument
// and we don't reference testNumber1 anymore

// Calling the retainArguments method happens too late.
[theInvocation retainArguments];

// This will most likely result in a bad access since the invocation references an invalid pointer or nil
[theInvocation invoke];

Поэтому мигрант говорит вам: На этом этапе вы должны явно обеспечить, чтобы ваш объект сохранялся достаточно долго. Поэтому создайте переменную unsafe_unretained (где вы должны иметь в виду, что ARC не справится с этим для вас).

Ответ 6

Согласно Apple Doc NSInvocation:

Этот класс не сохраняет аргументы для содержащегося вызова по умолчанию. Если эти объекты могут исчезнуть между моментом создания экземпляра NSInvocation и временем его использования, вы должны явно сохранить объекты самостоятельно или вызвать метод saveArguments, чтобы объект-вызов сохранял их сами.