Почему переносчик ARC говорит, что NSInvocation -setArgument: небезопасен, если аргумент не __unsafe_unretained? - PullRequest
34 голосов
/ 29 декабря 2011

Я переносил блок кода в автоматический подсчет ссылок (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's 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-переносчик говорит, что использование строго удерживаемого указателя в качестве аргумента для -setArgument: NSInvocation небезопасно, если вы не используете что-то, что __unsafe_unretained?

Ответы [ 6 ]

9 голосов
/ 30 декабря 2011

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

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

Из NSInvocation.h:

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

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

Например:

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
8 голосов
/ 25 марта 2013

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

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

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

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

2 голосов
/ 08 июля 2012

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

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

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

1 голос
/ 15 мая 2014

Важным моментом здесь является стандартное поведение 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 не будет управлять вами за вас).

1 голос
/ 30 декабря 2011

Приведу мое полное предположение здесь.

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

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

0 голосов
/ 27 июля 2013

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

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

...