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

Обработка вопросов собственности на указатель-указатель в ARC

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

@property (nonatomic, strong) Foo * bar;

Синтезируется в реализации как:

@synthesize bar = _bar;

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

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

Кроме того, предположим, что Object 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 "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

4b9b3361

Ответ 1

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

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

ARC сталкивается с одной и той же проблемой, и ее решение состоит в том, чтобы вывести или принять явный атрибут, определяющий право собственности на ссылочную переменную, а затем потребовать, чтобы вызывающий абонент предоставил ссылку на переменную соответствующего права собственности, которая должна быть передана. Этот последний бит может потребовать использования скрытых временных переменных. Это называется "наименее плохим решением" в спецификации и называется "pass-by-writeback" .

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

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

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

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

ARC указывает byRefFoo на тип Foo * __autoreleasing * - ссылку на ссылку на автореализацию. Это то, что требуется "pass-by-writeback".

Этот код только действителен, потому что temp является локальным. Было бы неправильно делать это с переменной экземпляра (как вы узнали в своем EDIT). Он также только действителен при условии, что параметр используется в стандартном "выходном" режиме, и любое обновленное значение присваивается при возврате 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: вызывает локальную переменную (называемую imaginatively 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");

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

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: требует ссылки на переменную autoreleasing, ARC создала новую переменную, адрес которой находится 0x7fff5fbfedb8, и скопировал ссылку на объект (0x100176f30) на это.

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

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

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

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

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

Примечание: эта обратная запись почему-то работает только для шаблона "out" с использованием pass-by-reference - вы не можете сохранить ссылку, переданную в 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

  • Поскольку он хранится в локальном, новый объект очищается 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 не уклонилась от сохранения /autorelease при назначении этой скрытой переменной, как это было выше. Вы можете увидеть авторезервирование в строке 7, но мой Breadcrumbs пропустил retain - но право собственности на 2 в строке 8 показало, что оно произошло.

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

Резюме

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

  • Это реализовано ARC, использующим метод pass-by-writeback, и только работает, если вы следуете шаблону параметра "out" . Если вы хотите сохранить переданную ссылку для использования позже, вам нужно будет сделать больше самостоятельно.

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

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

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


Добавление Апрель 2016: __block переменные

В комментариях Heath Borders спросила:

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

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

В спецификации указано:

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

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

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

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

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

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

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

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

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

Это кажется недокументированным.

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

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

Обоснование

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

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

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

Ответ 2

Это совершенно законно. Доступ к собственности не имеет значения; передача указателя на объект обычно выполняется с помощью объектов 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 остается в силе.