ARC и авто-релиз

autorelease используется для возвращаемого объекта функции, поэтому вызывающая сторона не вступает во владение, и вызываемая сторона освободит объект в будущем.

Тем не менее, ARC способен подсчитывать владение вызывающим абонентом и освобождать его после использования, то есть он может вести себя так же, как Smart Pointer в C++. С ARC он может избавиться от ауто-релиза, потому что ауто-релиз не является детерминированным.

Причина, по которой я задаю этот вопрос, заключается в том, что я вижу возвращенные вызовы объектов dealloc в ARC раньше, чем в коде, отличном от ARC. Это заставляет меня думать, что ARC может выглядеть как Smart Pointer и может сделать авто-релиз бесполезным. Это правда или возможно? Единственное, что я могу думать об использовании autorelease usefullness - это многопоточный или сетевой код, потому что подсчет владения может быть не так просто, когда объект обходит.

Спасибо за ваши мысли.

Вот новое редактирование, чтобы прояснить ситуацию:

с авто-выпуском

+ (MyClass*) myClass
{
    return [[[MyCClass alloc] init] autorelease];
}

- doSomething
{
   MyClass *obj = [MyClass myClass];
}

С ARC:

+ (MyClass*) myClass
{
    return [[MyCClass alloc] init]; // no autorelease
}

- doSomething
{
   MyClass *obj = [MyClass myClass];
   // insert [obj release]
}

Итак, нам действительно не нужен авто-релиз.

5 ответов

Авторелиз как механизм все еще используется ARC, кроме того, скомпилированный код ARC предназначен для беспрепятственного взаимодействия со скомпилированным кодом MRC, так что механизм авто-релиза существует.

Во-первых, не думайте с точки зрения количества ссылок, но с точки зрения доли владения - пока существует заявленная доля владения объектом, тогда объект живет, когда нет доли владения, он уничтожается. В MRC вы заявляете право собственности с помощью retainили путем создания нового объекта; и вы отказываетесь от права собственности, используя release,

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

Для решения этой проблемы используются два решения:

1) Объявлен метод для передачи интереса владельца в его возвращаемом значении от вызываемого к вызывающему - это модель, используемая для init, copyи др. методы. Вызываемый никогда не уведомляет, что он отказывается от своей доли владения, и вызываемый никогда не объявляет долю владения - по соглашению вызывающая сторона просто берет на себя долю владения и обязуется отказаться от нее позже.

2) Объявлен метод для возврата значения, в котором вызывающая сторона не имеет доли владения, но в которой кто-то еще будет сохранять долю владения в течение некоторого короткого периода времени - обычно до конца текущего цикла цикла выполнения. Если вызывающий объект хочет использовать возвращаемое значение дольше, чем это, он должен объявить свою собственную долю владения, но в противном случае он может рассчитывать на то, что кто-то другой имеет долю владения и, следовательно, объект остается рядом.

Вопрос в том, кем может быть этот "кто-то", кто поддерживает право собственности? Это не может быть вызываемый метод, так как он собирается уйти. Введите "пул автоматического выпуска" - это просто объект, которому любой может передать право владения, поэтому этот объект останется на некоторое время. Пул автоматического выпуска освободит свою долю владения всеми объектами, переданными ему таким образом, когда будет дано указание сделать это - обычно в конце текущего цикла цикла выполнения.

Теперь, если вышеизложенное имеет какой-то смысл (т. Е. Если я объяснил это ясно), вы можете видеть, что метод (2) на самом деле не требуется, поскольку вы всегда можете использовать метод (1); но, и это очень важно, но в MRC это намного больше работы для программиста - каждое значение, полученное от метода, имеет интерес владения, которым нужно управлять и в какой-то момент отказываться - генерировать строку только для ее вывода? Что ж, тогда вам нужно отказаться от интереса к этой временной строке... Итак (2) делает жизнь намного проще.

С другой стороны, компьютеры - просто быстрые идиоты, и для них хорошо подходят подсчет вещей и вставка кода, чтобы отказаться от интереса владельцев интеллектуальных программистов. Таким образом, ARC не нуждается в пуле автоматического выпуска. Но это может сделать вещи проще и эффективнее, и за кулисами ARC оптимизирует его использование - посмотрите на вывод ассемблера в XCode, и вы увидите вызовы подпрограмм с именем, похожим на "retainAutoreleasedReturnValue"...

Таким образом, вы правы, это не нужно, но все же полезно - но в ARC вы можете (обычно) забыть, что оно существует.

HTH больше, чем это, вероятно, смущает!

autorelease используется для возвращаемого объекта функции, поэтому вызывающая сторона не вступает во владение, и вызываемая сторона освободит объект в будущем.

Если автоматически выпущено, оно будет добавлено в пул автоматического выпуска. Когда пул авто-выпуска будет слит, будет выполнено отложенное освобождение. функция / метод не должна возвращать объект с автоматическим освобождением (например, это может быть ивар, который не получил цикл сохранения / автоматического выпуска).

Тем не менее, ARC способен подсчитывать владение вызывающим абонентом и освобождать его после использования, то есть он может вести себя так же, как Smart Pointer в C++. С ARC он может избавиться от ауто-релиза, потому что ауто-релиз не является детерминированным.

Это имеет потенциал для. Там нет гарантии. Самая большая "проблема" заключается в том, что компилятор не знает / не заботится о механике памяти возвращаемого объекта произвольного вызова. Он не может предположить, как возвращается объект, потому что ARC является новым дополнением, предшествующим MRC. Это важно, потому что это делает программы ARC совместимыми с программами, которые используют ручное сохранение / отпускание. Например, Foundation.framework может использовать ARC или MRC, или он может использовать оба. Он также может вызывать API, которые были построены с использованием старых наборов инструментов. Таким образом, это имеет преимущество в том, что тонна существующего кода может быть использована.

Причина, по которой я задаю этот вопрос, заключается в том, что я вижу возвращенные вызовы объектов dealloc в ARC раньше, чем в коде, отличном от ARC.

Существует необязательный способ возврата объекта - см. Ответ CRD (+1) о сборке и вызовы, которые компилятор вставляет для выполнения операций подсчета ссылок, например retainAutoreleasedReturnValue,

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

Это заставляет меня думать, что ARC может выглядеть как Smart Pointer и может сделать авто-релиз бесполезным. Это правда или возможно?

Теоретически, я не понимаю, почему пулы с автоматическим выпуском не могли быть отменены для новой системы. Тем не менее, я думаю, что существует слишком много существующего кода, который полагается на пулы автоматического выпуска, чтобы снять это ограничение - я думаю, что им нужно будет поэтапно ввести новый исполняемый формат (как в случае с ObjC Garbage Collection) и просмотреть тонну существующих API и программы для такого значительного перехода к успеху. Кроме того, несколько API, вероятно, просто необходимо удалить. API могут нуждаться в некотором усилении в отношении владения для достижения этой цели, но большая часть этого завершена в программах, которые уже были перенесены в ARC. Черт возьми, даже компилятор может (быть расширен) внутренне использовать форму умных указателей для передачи и возврата типов объектов, а пулы автоматического выпуска могут быть исключены в такой системе. Опять же, для этого потребуется перенести много кода. Поэтому такое обновление будет похоже на ARC V2.

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

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

autorelease все еще используется под ARC. ARC просто звонит вам и умен, чтобы замкнуть его. Вот демонстрация того, как именно это работает, и я скопирую здесь на случай, если этот пост в блоге когда-либо исчезнет; все благодаря Мэтту Галлоуэю.

Итак, рассмотрим следующий метод:

void foo() {
    @autoreleasepool {
        NSNumber *number = [NSNumber numberWithInt:0];
        NSLog(@"number = %p", number);
    }
}

Конечно, это полностью надумано, но это должно позволить нам увидеть, что происходит. В земле без ARC мы предположили бы, что число будет выделено внутри numberWithInt: и возвращено автоматически освобожденным. Таким образом, когда пул авторелизов будет в следующий раз истощен, он будет освобожден. Итак, давайте посмотрим, произошло ли это (как обычно, это инструкции ARMv7):

    .globl  _foo
    .align  2
    .code   16
    .thumb_func     _foo
_foo:
    push    {r4, r7, lr}
    add     r7, sp, #4
    blx     _objc_autoreleasePoolPush
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
    movs    r2, #0
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
    mov     r4, r0
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
LPC0_0:
    add     r1, pc
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
LPC0_1:
    add     r0, pc
    ldr     r1, [r1]
    ldr     r0, [r0]
    blx     _objc_msgSend
    mov     r1, r0
    movw    r0, :lower16:(L__unnamed_cfstring_-(LPC0_2+4))
    movt    r0, :upper16:(L__unnamed_cfstring_-(LPC0_2+4))
LPC0_2:
    add     r0, pc
    blx     _NSLog
    mov     r0, r4
    blx     _objc_autoreleasePoolPop
    pop     {r4, r7, pc}

Ну да. Это именно то, что происходит. Мы можем видеть вызов, чтобы выдвинуть пул автоматического выпуска, затем вызов numberWithInt: затем вызов, чтобы вытолкнуть пул автоматического выпуска. Именно то, что мы ожидали. Теперь давайте посмотрим на точно такой же код, скомпилированный в ARC:

    .globl  _foo
    .align  2
    .code   16
    .thumb_func     _foo
_foo:
    push    {r4, r5, r7, lr}
    add     r7, sp, #8
    blx     _objc_autoreleasePoolPush
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
    movs    r2, #0
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
    mov     r4, r0
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
LPC0_0:
    add     r1, pc
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
LPC0_1:
    add     r0, pc
    ldr     r1, [r1]
    ldr     r0, [r0]
    blx     _objc_msgSend
    @ InlineAsm Start
    mov     r7, r7          @ marker for objc_retainAutoreleaseReturnValue
    @ InlineAsm End
    blx     _objc_retainAutoreleasedReturnValue
    mov     r5, r0
    movw    r0, :lower16:(L__unnamed_cfstring_-(LPC0_2+4))
    movt    r0, :upper16:(L__unnamed_cfstring_-(LPC0_2+4))
    mov     r1, r5
LPC0_2:
    add     r0, pc
    blx     _NSLog
    mov     r0, r5
    blx     _objc_release
    mov     r0, r4
    blx     _objc_autoreleasePoolPop
    pop     {r4, r5, r7, pc}

Обратите внимание на вызовы objc_retainAutoreleasedReturnValue и objc_release. Там происходит то, что ARC определил для нас, что на самом деле ему не нужно беспокоиться о пуле автоматического выпуска, потому что он может просто сказать, что автоматического выпуска не происходит (с вызовом objc_retainAutoreleasedReturnValue), а затем освободить объект позже. сам. Это желательно, поскольку это означает, что логика автоматического выпуска не должна происходить.

Обратите внимание, что пул автоматического освобождения все еще должен быть выдвинут и вытолкнут, потому что ARC не может знать, что происходит в вызовах numberWithInt: и NSLog, чтобы знать, будут ли объекты помещены в пул там. Если бы он знал, что они ничего не выпускали автоматически, он мог бы на самом деле избавиться от толчка и удара. Возможно, такая логика появится в будущих версиях, хотя я не совсем уверен, как будет работать семантика этого.

Теперь давайте рассмотрим другой пример, в котором мы хотим использовать число вне области блока пула автоматического выпуска. Это должно показать нам, почему ARC - это чудо для работы. Рассмотрим следующий код:

void bar() {
    NSNumber *number;
    @autoreleasepool {
        number = [NSNumber numberWithInt:0];
        NSLog(@"number = %p", number);
    }
    NSLog(@"number = %p", number);
}

Вы можете (правильно) думать, что это вызовет проблемы, даже если это выглядит совершенно безобидным. Это проблема, потому что номер будет выделен внутри блока пула автоматического выпуска, будет освобожден, когда пул автоматического выпуска, но затем будет использован после того, как он был освобожден. Ой! Давайте посмотрим, правы ли мы, скомпилировав его без ARC:

    .globl  _bar
    .align  2
    .code   16
    .thumb_func     _bar
_bar:
    push    {r4, r5, r6, r7, lr}
    add     r7, sp, #12
    blx     _objc_autoreleasePoolPush
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
    movs    r2, #0
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
    mov     r4, r0
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
LPC1_0:
    add     r1, pc
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
LPC1_1:
    add     r0, pc
    ldr     r1, [r1]
    ldr     r0, [r0]
    blx     _objc_msgSend
    movw    r6, :lower16:(L__unnamed_cfstring_-(LPC1_2+4))
    movt    r6, :upper16:(L__unnamed_cfstring_-(LPC1_2+4))
LPC1_2:
    add     r6, pc
    mov     r5, r0
    mov     r1, r5
    mov     r0, r6
    blx     _NSLog
    mov     r0, r4
    blx     _objc_autoreleasePoolPop
    mov     r0, r6
    mov     r1, r5
    blx     _NSLog
    pop     {r4, r5, r6, r7, pc}

Очевидно, что никаких вызовов для сохранения, освобождения или автоматического выпуска, как мы и ожидали, мы не сделали явно, и мы не используем ARC. Здесь мы видим, что он был скомпилирован именно так, как мы ожидали от наших рассуждений ранее. Итак, давайте посмотрим, как это выглядит, когда ARC протягивает нам руку помощи:

    .globl  _bar
    .align  2
    .code   16
    .thumb_func     _bar
_bar:
    push    {r4, r5, r6, r7, lr}
    add     r7, sp, #12
    blx     _objc_autoreleasePoolPush
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
    movs    r2, #0
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
    mov     r4, r0
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
LPC1_0:
    add     r1, pc
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
LPC1_1:
    add     r0, pc
    ldr     r1, [r1]
    ldr     r0, [r0]
    blx     _objc_msgSend
    @ InlineAsm Start
    mov     r7, r7          @ marker for objc_retainAutoreleaseReturnValue
    @ InlineAsm End
    blx     _objc_retainAutoreleasedReturnValue
    movw    r6, :lower16:(L__unnamed_cfstring_-(LPC1_2+4))
    movt    r6, :upper16:(L__unnamed_cfstring_-(LPC1_2+4))
LPC1_2:
    add     r6, pc
    mov     r5, r0
    mov     r1, r5
    mov     r0, r6
    blx     _NSLog
    mov     r0, r4
    blx     _objc_autoreleasePoolPop
    mov     r0, r6
    mov     r1, r5
    blx     _NSLog
    mov     r0, r5
    blx     _objc_release
    pop     {r4, r5, r6, r7, pc}

Аплодисменты для ARC, пожалуйста! Обратите внимание, что мы поняли, что мы используем число вне области блока пула автоматического выпуска, поэтому оно сохраняет возвращаемое значение из numberWithInt: так же, как и раньше, но на этот раз оно помещает выпуск в конец функции бара, а не до пул автозапуска выскочил. Это спасло бы нас от сбоев в каком-то коде, который мы могли бы считать правильным, но на самом деле имелась небольшая ошибка управления памятью.

Разница между ARC и autorelease объясняется в коде:

ARC:

-somefunc {
  id obj = [NSArray array];
  NSLog(@"%@", obj);
  // ARC now calls release for the first object

  id obj2 = [NSArray array];
  NSLog(@"%@", obj2);
  // ARC now calls release for the second object
}

Авто-релиз:

-somefunc {
  id obj = [NSArray array];
  NSLog(@"%@", obj);

  id obj2 = [NSArray array];
  NSLog(@"%@", obj2);
}
// Objects are released some time after this

По сути, ARC работает, когда переменная больше не используется в области видимости, тогда как autorelease ждет, пока не достигнет основного цикла, а затем вызывает release на все объекты в бассейне. ARC используется внутри области действия, autorelease используется вне области действия функции.

Тем не менее, ARC способен подсчитывать владение вызывающим абонентом и освобождать его после использования, то есть он может вести себя так же, как Smart Pointer в C++. С ARC он может избавиться от ауто-релиза, потому что ауто-релиз не является детерминированным.

Вы путаете ARC с подсчетом ссылок. Objective-C всегда полагался на подсчет ссылок для управления памятью. ARC продолжает эту традицию и просто избавляет программиста от необходимости вручную вставлять соответствующие вызовы -retain, -release, а также -autorelease, Под ARC компилятор вставляет эти вызовы для вас, но механизм подсчета ссылок остается таким же, как и всегда.

ARC не устраняет необходимость в авто-выпуске, но может быть в состоянии избежать его в ситуациях, когда человек обычно использовал бы его.

Другие вопросы по тегам