Решение проблем владения указателем на указатель в ARC - PullRequest
41 голосов
/ 11 января 2012

Предположим, Объект A имеет свойство:

@property (nonatomic, strong) Foo * bar;

Синтезирован в реализации как:

@synthesize bar = _bar;

Объект B манипулируетFoo **, как в этом примере вызов из Объект A :

Foo * temp = self.bar;
[objB doSomething:&temp];
self.bar = temp;
  • Может ли это или что-то подобное быть сделано законно?
  • Что такоеправильное объявление для метода doSomething:?

Кроме того, предположим, что Объект B может быть освобожден до того, как у меня появится возможность установить свойство bar (и таким образом принятьправо собственности на экземпляр, на которое указывает temp) - Как бы я сказал ARC передать ссылку на владельца?Другими словами, если бы я хотел, чтобы следующий пример кода работал, как мне нужно было бы решать проблемы ARC?

Foo * temp = self.bar;    // Give it a reference to some current value
[objB doSomething:&temp]; // Let it modify the reference
self.bar = nil;           // Basically release whatever we have
_bar = temp;              // Since we're getting back an owning reference, bypass setter
  • О чем я не думаю?

РЕДАКТИРОВАТЬ

Основываясь на ответе @KevinBallard, я просто хочу подтвердить свое понимание.Это правильно?

Объект A:

@implementation ObjectA

@synthesize bar = _bar;

- (void)someMethod
{
    ObjectB * objB = [[ObjectB alloc] initWithFoo:&_bar];
    // objB handed off somewhere and eventually it's "doSomething" method is called.
}

@end

Объект B:

@implementation ObjectB
{
    Foo * __autoreleasing * _temp;
}

- (id)initWithFoo:(Foo * __autoreleasing *)temp
{
    id self = [super init];
    if (self)
    {
        _temp = temp;
    }
    return self;
}

- (void)doSomething
{
    ...
    *_temp = [[Foo alloc] init]; 
    ...
}

@end

Это создает компиляциюошибка: passing address of non-local object to __autoreleasing parameter for write-back

Ответы [ 2 ]

142 голосов
/ 12 января 2012

ARC должен знать, кто является владельцем ссылки на объект, чтобы он мог определить, когда ее освободить и т. Д. Для любой переменной (локальной, экземпляровой или глобальной) ARC имеет правила для определения владельца;либо выводом, либо явным атрибутом.Это равносильно тому, что программист должен отслеживать владение до ARC.

Но что произойдет, если у вас будет ссылка на переменную?Вы не можете (до ARC) самостоятельно писать код, который принимает ссылку на переменную и который всегда будет работать правильно, независимо от владельца этой переменной - поскольку вы не могли знать, нужно ли вам выпускать и т. Д. Т.е. вы не можете создать кодкоторая работает для переменной (в смысле изменения!) неизвестного владельца.

ARC сталкивается с той же проблемой, и ее решение состоит в том, чтобы вывести или принять явный атрибут, определяющий владение указанной переменной, а затем потребовать вызывающий абонент для организации ссылки на переменную соответствующего владельца, которая будет передана.Этот последний бит может потребовать использования скрытых временных переменных.В спецификации это называется «наименее плохим решением» и называется «передача по обратной записи».

Первая часть вопроса:

Foo * temp = self.bar;
[objB doSomething:&temp];
self.bar = temp;
  • Может ли это или что-то подобное быть выполнено законно?

Да, ARC хорошо обрабатывает код.temp подразумевается как strong, и некоторые закулисные вещи передают его со ссылкой на doSomething:.

  • Какое правильное объявление для метода doSomething:?
- (void) doSomething:(Foo **)byRefFoo

ARC выводит byRefFoo типа Foo * __autoreleasing * - ссылка на ссылку для автоматического освобождения.Это то, что требуется для «обратной передачи».

Этот код является только , действительным, поскольку temp является локальным.Было бы неправильно делать это с переменной экземпляра (как вы узнали в вашей EDIT).Также допустимо только при условии, что параметр используется в стандартном режиме «out», и любому обновленному значению присваивается , когда doSomething: возвращает .И то, и другое объясняется тем, что способ передачи с обратной записью работает как часть этого «наименее плохого решения» ...

Сводка : при использовании локальных переменных они могут передаваться по ссылке дляиспользуйте в стандартном шаблоне «out» с ARC, выводя все необходимые атрибуты и т. д.

Под капотом

Вместо Foo вопроса мы будем использовать тип Breadcrumbs;по сути, это обернутый NSString, который отслеживает каждые init, retain, release, autorelease и dealloc (почти как вы увидите ниже), чтобы мы могли видеть, что происходит.То, как написано Breadcrumbs, не является материальным.

Теперь рассмотрим следующий класс:

@implementation ByRef
{
   Breadcrumbs *instance;                                // __strong inferred
}

Метод для изменения значения, переданного по ссылке:

- (void) indirect:(Breadcrumbs **)byRef                  // __autoreleasing inferred
{
   *byRef = [Breadcrumbs newWith:@"banana"];
}

Простая оболочка для indirect:, чтобы мы могли видеть, что она передается и когда она возвращает:

- (void) indirectWrapper:(Breadcrumbs **)byRef           // __autoreleasing inferred
{
   NSLog(@"indirect: passed reference %p, contains %p - %@, owners %lu", byRef, *byRef, *byRef, [*byRef ownerCount]);
   [self indirect:byRef];
   NSLog(@"indirect: returned");
}

И метод для демонстрации indirect:, вызываемый локальной переменной (называемый образно local):

- (void) demo1
{
   NSLog(@"Strong local passed by autoreleasing reference");
   Breadcrumbs *local;                                   // __strong inferred
   local = [Breadcrumbs newWith:@"apple"];
   NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
   [self indirectWrapper:&local];
   NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
}

@end

Теперь немного кода для осуществления demo1 локализации пула автоматического выпуска, чтобы мы могли видеть, что выделяется, освобождается и когда:

ByRef *test = [ByRef new];

NSLog(@"Start demo1");
@autoreleasepool
{
   [test demo1];
   NSLog(@"Flush demo1");
}
NSLog(@"End demo1");

Выполнение вышеупомянутого производит следующее наconsole:

ark[2041:707] Start demo1
ark[2041:707] Strong local passed by autoreleasing reference
ark[2041:707] >>> 0x100176f30: init
ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1
ark[2041:707] indirect: passed reference 0x7fff5fbfedb8, contains 0x100176f30 - apple, owners 1
ark[2041:707] >>> 0x100427d10: init
ark[2041:707] >>> 0x100427d10: autorelease
ark[2041:707] indirect: returned
ark[2041:707] >>> 0x100427d10: retain
ark[2041:707] >>> 0x100176f30: release
ark[2041:707] >>> 0x100176f30: dealloc
ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d10 - banana, owners 2
ark[2041:707] >>> 0x100427d10: release
ark[2041:707] Flush demo1
ark[2041:707] >>> 0x100427d10: release
ark[2041:707] >>> 0x100427d10: dealloc
ark[2041:707] End demo1

[Строки ">>>" взяты из Breadcrumbs.] Просто следуйте адресам объектов (0x100 ...) и переменных (0x7fff...) и все понятно ...

Ну может и нет!Здесь снова комментарии с каждым блоком:

ark[2041:707] Start demo1
ark[2041:707] Strong local passed by autoreleasing reference
ark[2041:707] >>> 0x100176f30: init
ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1

Здесь мы видим, что [Breadcrumbs newWith:@"apple"] создает объект по адресу 0x100176f30.Это хранится в local, чей адрес 0x7fff5fbfedc0, и объект имеет 1 владельца (local).

ark[2041:707] indirect: passed reference 0x7fff5fbfedb8, contains 0x100176f30 - apple, owners 1

Здесь появляется скрытая переменная: так как indirect: требует ссылки напеременная автоматического освобождения ARC создала новую переменную с адресом 0x7fff5fbfedb8 и скопировала ссылку на объект (0x100176f30) в нее.

ark[2041:707] >>> 0x100427d10: init
ark[2041:707] >>> 0x100427d10: autorelease
ark[2041:707] indirect: returned

Внутри indirect: создается новый объект, и ARC автоматически освобождает его перед назначением - потому что переданные ссылки ссылаются на переменную автоматического освобождения.

Примечание: ARC не делаетнужно что-либо делать с предыдущим содержимым (0x100176f30) указанной переменной (0x7fff5fbfedb8), так как оно автоматически высвобождает и, следовательно, не несет за это ответственность.То есть, что означает «владение авто-релизом», так это то, что любая назначенная ссылка уже была эффективно автоматически выпущена.Вы увидите, что при создании скрытой переменной ARC фактически не сохраняла и автоматически высвобождала ее содержимое - ей не нужно было этого делать, поскольку она знала, что существует сильная ссылка (в local) на объект, которым она управляет.[В последнем примере ниже ARC не исключает сохранение / авто-выпуск.]

ark[2041:707] >>> 0x100427d10: retain
ark[2041:707] >>> 0x100176f30: release
ark[2041:707] >>> 0x100176f30: dealloc

Эти действия являются результатом копирования («обратной записи» при вызове при обратной записи) значения из скрытогопеременная в local.Release / dealloc для старой сильной ссылки в local, а сохранение для объекта, на который ссылается скрытая переменная (которая была автоматически освобождена indirect:)

Примечание: это обратная запись, поэтому она работает только для шаблона "out" использования передачи по ссылке - вы не можете сохранить ссылку, переданную в indirect:, как на скрытую локальную переменную, которая вот-вот исчезнет...

ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d10 - banana, owners 2

Поэтому после вызова local ссылается на новый объект, и у него есть 2 владельцев - local приходится на одного, а другой -autorelease в indirect:

ark[2041:707] >>> 0x100427d10: release

demo1 теперь завершено, поэтому ARC освобождает объект в local

ark[2041:707] Flush demo1
ark[2041:707] >>> 0x100427d10: release
ark[2041:707] >>> 0x100427d10: dealloc
ark[2041:707] End demo1

и после demo1 возвращает локализованный @autoreleasepool обрабатывает автоматическое освобождение в ожидании от indirect:, теперь владение равно нулю, и мы получаем dealloc.

Передача переменных экземпляра по ссылке

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

  • скопировать переменную вашего экземпляра в локальную

  • добавить некоторые атрибуты

Чтобы продемонстрировать второе, мы добавляем к классу ByRef a strongIndirect:, который указывает, что для этого требуется ссылка на сильную переменную:

- (void) strongIndirect:(Breadcrumbs * __strong *)byRef
{
   *byRef = [Breadcrumbs newWith:@"plum"];
}

- (void) strongIndirectWrapper:(Breadcrumbs * __strong *)byRef
{
   NSLog(@"strongIndirect: passed reference %p, contains %p - %@, owners %lu", byRef, *byRef, *byRef, [*byRef ownerCount]);
   [self strongIndirect:byRef];
   NSLog(@"strongIndirect: returned");
}

и соответствующий demo2, который использует ByRefпеременная экземпляра (снова с образным именем instance):

- (void) demo2
{
   NSLog(@"Strong instance passed by strong reference");
   instance = [Breadcrumbs newWith:@"orange"];
   NSLog(@"instance: addr %p, contains %p - %@, owners %lu", &instance, instance, instance, [instance ownerCount]);
   [self strongIndirectWrapper:&instance];
   NSLog(@"instance: addr %p, contains %p - %@, owners %lu", &instance, instance, instance, [instance ownerCount]);
}

Выполните это с похожим фрагментом кода, как для demo1 выше, и мы получим:

1  ark[2041:707] Start demo2
2  ark[2041:707] Strong instance passed by strong reference
3  ark[2041:707] >>> 0x100176f30: init
4  ark[2041:707] instance: addr 0x100147518, contains 0x100176f30 - orange, owners 1
5  ark[2041:707] strongIndirect: passed reference 0x100147518, contains 0x100176f30 - orange, owners 1
6  ark[2041:707] >>> 0x100427d10: init
7  ark[2041:707] >>> 0x100176f30: release
8  ark[2041:707] >>> 0x100176f30: dealloc
9  ark[2041:707] strongIndirect: returned
10 ark[2041:707] instance: addr 0x100147518, contains 0x100427d10 - plum, owners 1
11 ark[2041:707] Flush demo2
12 ark[2041:707] End demo2

Что немного короче, чем раньше.Это по двум причинам:

  • Поскольку мы передаем сильную переменную (instance) методу (strongIndirect:), который ожидает ссылку на сильную переменную, в этом нет необходимости.для ARC использовать скрытую переменную - переменные в строке 4 и 5 выше одинаковы (0x100147518).

  • Поскольку ARC знает, что указанная переменная в strongIndirect: сильна тамнет необходимости хранить автоматически освобожденную ссылку в strongIndirect: и затем записывать ее обратно после вызова - ARC просто выполняет стандартное строгое назначение, строки 6-8, и нет ничего для автоматического освобождения позже (между строками 11 и 12).

Работает ли strongIndirect: для сильных местных жителей?

Конечно, вот demo3:

- (void) demo3
{
   NSLog(@"Strong local passed by strong reference");
   Breadcrumbs *local;                                   // __strong inferred
   local = [Breadcrumbs newWith:@"apple"];
   NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
   [self strongIndirectWrapper:&local];
   NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
}

Выполнение этого с нашей стандартной оболочкой дает:

1  ark[2041:707] Start demo3
2  ark[2041:707] Strong local passed by strong reference
3  ark[2041:707] >>> 0x100176f30: init
4  ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1
5  ark[2041:707] strongIndirect: passed reference 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1
6  ark[2041:707] >>> 0x100427d20: init
7  ark[2041:707] >>> 0x100176f30: release
8  ark[2041:707] >>> 0x100176f30: dealloc
9  ark[2041:707] strongIndirect: returned
10 ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d20 - plum, owners 1
11 ark[2041:707] >>> 0x100427d20: release
12 ark[2041:707] >>> 0x100427d20: dealloc
13 ark[2041:707] Flush demo3
14 ark[2041:707] End demo3

Это почти то же самое, что и в предыдущем примере, только два небольших различия:

  • Адрес локального настек передается (0x7fff5fbfedc0), строки 4 и 5

  • Как это хранитьd в локальной системе новый объект очищается ARC, строки 11 и 12

Почему бы не всегда добавлять __strong к ссылочным аргументам?

Одна причина в том, что не все так сильно!Передача обратной записи ARC работает и для слабых местных жителей.Наша последняя демонстрация:

- (void) demo4
{
   NSLog(@"Weak instance passed by autoreleasing reference");
   instance = [Breadcrumbs newWith:@"peach"];
   Breadcrumbs __weak *weakLocal = instance;
   NSLog(@"weakLocal: addr %p, contains %p - %@, owners %lu", &weakLocal, weakLocal, weakLocal, [weakLocal ownerCount]);
   [self indirectWrapper:&weakLocal];
   NSLog(@"weakLocal: addr %p, contains %p -, %@, owners %lu", &weakLocal, weakLocal, weakLocal, [weakLocal ownerCount]);
}

[Здесь мы только что использовали instance, поэтому у нас есть кое-что, на что мы можем сделать слабую ссылку.]

Выполнение этого с нашей стандартной оболочкой приводит к:

1  ark[2041:707] Start demo4
2  ark[2041:707] Weak instance passed by autoreleasing reference
3  ark[2041:707] >>> 0x100427d20: init
4  ark[2041:707] >>> 0x100427d10: release
5  ark[2041:707] >>> 0x100427d10: dealloc
6  ark[2041:707] weakLocal: addr 0x7fff5fbfedd0, contains 0x100427d20 - peach, owners 1
7  ark[2041:707] >>> 0x100427d20: autorelease
8  ark[2041:707] indirect: passed reference 0x7fff5fbfedc8, contains 0x100427d20 - peach, owners 2
9  ark[2041:707] >>> 0x100429040: init
10 ark[2041:707] >>> 0x100429040: autorelease
11 ark[2041:707] indirect: returned
12 ark[2041:707] weakLocal: addr 0x7fff5fbfedd0, contains 0x100429040 -, banana, owners 1
13 ark[2041:707] Flush demo4
14 ark[2041:707] >>> 0x100429040: release
15 ark[2041:707] >>> 0x100429040: dealloc
16 ark[2041:707] >>> 0x100427d20: release
17 ark[2041:707] End demo4

Примечания:

  • Строки 3-5 просто устанавливают instance - создать новое значениеи освободить старый - реальный материал начинается со строки 6

  • ARC использует скрытую переменную (строка 8, 0x7fff5fbfedc8) для слабых местных жителей (строка 6, 0x7fff5fbfedd0) какскважина

  • ARC не исключила сохранение / авто-выпуск при назначении этой скрытой переменной, как это было выше.Вы можете увидеть авторелиз в строке 7, но мой Breadcrumbs пропустил retain - но владение 2 в строке 8 показывает, что это произошло.

  • Существует два авто-релиза, поэтому необходимодве соответствующие версии, когда пул очищается (строки 14 и 16) - существует только одна соответствующая освобождение (строка 15), так как на другой объект (0x100427d20) ссылается instance, и ARC очищает его, когда наша ByRef экземпляр исчезает.

Сводка

  • Без каких-либо добавленных атрибутов ARC будет делать правильные вещи для локальных (выведеносильные) переменные, передаваемые в качестве параметров по ссылке (предполагаемое автоматическое освобождение).(И «local» включает в себя параметры текущего метода.)

  • Это реализовано ARC с использованием обратной передачи, и только работает, если вы следуете "Out "шаблон параметров.Если вы хотите сохранить переданную ссылку для последующего использования, вам нужно будет сделать больше самостоятельно.

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

  • передача с обратной записью также работает для __weak локальных.

Надеюсь, что поможет.


Приложение Апр 2016: __block переменные

В комментариях Хит Бордерс спросил:

Что если мойлокальная переменная имеет тип __block?Я почти уверен, что этот случай совпадает с переменной экземпляра в том смысле, что мне нужно либо скопировать их в локальные данные, либо присвоить тип принимающего параметра с помощью __strong, но мне любопытно мнение другого человека.

Интересный вопрос.

Спецификация гласит:

Передача при обратной записи некорректна, если выражение аргумента не имеет выражениядопустимая форма:

&var, где var - скалярная переменная продолжительности автоматического хранения с типом указателя сохраняемого объекта

Локальные переменные в (Objective-) C попо умолчанию имеют длительность автоматического хранения - они автоматически создаются и уничтожаются при входе / выходе их включающей функции / метода / блока.В ответе выше, когда мы ссылаемся на «локальную переменную», мы неявно ссылаемся на локальные переменные с автоматической продолжительностью хранения.

Локальные переменные могут быть объявлены с помощью квалификатора хранилища или хранилищаспецификатор класса для изменения продолжительности хранения переменной.Наиболее часто встречающийся - static;локальные переменные с статической продолжительностью хранения существуют во время выполнения программы, но доступны только (напрямую) в их локальной области действия.

Если вы пытаетесь передать локальную переменную static с помощью pass-by-writeback компилятор выдаст ошибку, указывающую, что переменная не имеет автоматической продолжительности хранения.Вы должны обрабатывать такие переменные так же, как переменные экземпляра (которые имеют выделенная продолжительность хранения ).

__block квалификатор хранения был введен в (Objective-) C как часть блоков и спецификация гласит:

Спецификатор хранения __block является взаимоисключающим для существующих локальных квалификаторов хранения auto, register и static.Переменные, определенные как __block, действуют так, как если бы они находились в выделенном хранилище, и это хранилище автоматически восстанавливается после последнего использования указанной переменной.

Таким образом, локальная переменная __block действует так, как будто она имеет выделенную длительность хранения, точно так же, как переменные экземпляра, и поэтому из-за спецификации обратной передачи такая переменная не может использоваться, так как она не имеет автоматической продолжительности хранения. .

Однако с инструментами, действующими на момент написания (Xcode 7.2, Clang 7.0.2) __block квалифицированные локальные переменные поддерживаются путем обратной записи и обрабатываются так же, как и с длительность автоматического хранения - используется скрытый __autoreleasing временный.

Это, похоже, недокументировано.

Сказав, что его "безопасно" использовать в том смысле, что он будет компилироваться или нет, и после компиляции код будет работать, даже если инструменты изменятся и его не удастся снова скомпилировать в будущем ... (при по крайней мере, без обработки переменной то же самое, что переменные экземпляра должны быть обработаны).

Причина, по которой он может быть принят, может быть найдена из обоснования ограничений на передачу по обратной записи (выделение добавлено):

Обоснование

Ограничение в форме аргумента служит двум целям. Во-первых, это делает невозможным передачу адреса массива в аргумент, который служит для защиты от серьезного риска неверного вывода аргумента «массива» в качестве выходного параметра. Во-вторых, значительно снижается вероятность того, что пользователь увидит проблемы с наложением алиасов из-за нижеприведенной реализации, где его сохранение во временной записи не сразу видно в исходной переменной аргумента.

Нет технической причины, по которой переменные экземпляра не могли бы поддерживаться передачей с обратной записью, но это может сбивать с толку из-за псевдонимов. __block переменные находятся где-то между автоматическими и выделенными переменными, поэтому, возможно, нынешние авторы инструментов предпочитают группировать их с первым, а не последним, для передачи с обратной записью.

Примечание: Читатели, знакомые с реализацией блоков, будут знать, что квалифицированный локальный __block может быть реализован как оптимизация с автоматическим или распределенным сроком хранения, в зависимости от использования, и поэтому задается вопросом, является ли это влияет на их использование для обратной записи. Похоже, это не так.

6 голосов
/ 11 января 2012

Это совершенно законно. Доступ к собственности не имеет значения; Передача указателя на объект обычно выполняется с помощью NSError* объектов.

Правильный способ объявления вашего метода:

- (returntype)doSomething:(Foo * __autoreleasing *)arg;

Это объявляет его как указатель на объект __autoreleasing, что в основном означает, что объект, на который указывает объект, предположительно был -autorelease d.

Что касается "Более того", это не проблема в рамках ARC. Ваша линия

Foo * temp = self.bar;

эквивалентно

__strong Foo *temp = self.bar;

что, я надеюсь, для вас очевидно, что это делает temp сильной ссылкой, и, таким образом, он "владеет" своим значением до тех пор, пока существует переменная. Другими словами, вы можете сказать

Foo *temp = self.bar;
self.bar = nil;

и temp все еще действительны.

...