Сборщик мусора в iOS? Нет! Эффективное управление памятью с помощью ARC. Часть 1.

Елизавета Гордиенко

Apple Inc. представила технологию автоматического подсчета ссылок в 2011 году и рекомендовала ARC к использованию в новых проектах, а также перевод существующего кода под управление ARC.

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

Перед тем как приступить непосредственно к рассмотрению ARC, рассмотрим структуру компилятора, предоставляющего данную технологию.

О компиляторе

LLVM compiler – компилятор нового поколения, базирующийся на open source проекте LLVM.org.

Clang – фронт энд LLVM для семейства языков C.

В Xcode LLVM compiler использует фронт энд Clang для разбора исходного кода и перевода его в промежуточный формат. Затем кодогенератор LLVM (бэк энд) переводит код из промежуточного формата в машинный код. Xcode также содержит LLVM GCC compiler, использующий фронт энд GCC compiler для максимальной совместимости, и бэк энд LLVM, использующий преимущества передового кодогенератора LLVM.

Xcode использует модифицированную версию GNU Compiler Collection, а также, начиная с Xcode 3.1, LLVM-GCC компилятор с фронт эндами из GNU Compiler Collection и генератором кода на основе LLVM, а начиная с Xcode 3.2, Apple LLVM Compiler с фронтэндом Clang и генератором кода на основе LLVM, а также Clang Static Analyzer.

С помощью Apple LLVM код компилируется в два раза быстрее, чем при использовании GCC. Полученные приложения также работают быстрее. [1, 4]

Статический анализатор Clang (Clang Static Analyzer) действительно полезный инструмент для поиска в коде ошибок, связанных с управлением памятью. Но раз анализатор может находить такие ошибки, то почему бы ему также и не исправить их за разработчика?

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

Automatic Reference Counting (ARC)

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

Использование автоматического управления памятью позволяет сделать разработку приложений более продуктивной, упростить дальнейшую поддержку кода и обеспечить большую стабильность приложений.

ARC не предоставляет cycle collector, что требует явного управления временем жизни объектов и разрыва циклов retain вручную или с использованием weak и unsafe ссылок.
img1

Появление

ARC поддерживается с Xcode 4.2, Mac OS X 10.6 “Snow Leopard” и iOS 4.0, однако только с Mac OS X 10.7 “Lion” и iOS 5 доступны все возможности ARC (в OS X v10.6 и iOS 4 не поддерживаются weak references).

Технология ARC должна заменить garbage collection в Mac приложениях, начиная с OS X Mountain Lion v10.8. [2]

ARC – это следующая ступень эволюции Objective-C, позволяющая сделать разработку более продуктивной, код более безопасным, приложения более стабильными. В связи с этим использование ARC и перевод существующего кода под управление ARC настоятельно рекомендуется Apple.

Поддержка

ARC включается флагом компилятора -fobjc-arc (выключается флагом -fno-objc-arc). В одном проекте можно сочетать классы с ручным и с автоматическим управлением памятью.

Если ARC включен, __has_feature(objc_arc) преобразуется препроцессором в 1.

Коротко о механизме

ARC не сборщик мусора. Это означает, что не происходит сканирование кучи (heap), нет связанных с этим пауз при выполнении приложения, нет недетерминированных вызовов release.

ARC не автоматизирует malloc/free, а также управление указателями типа CF или любыми другими retainable C указателями. Так как фактически ARC даже не может отличить такие типы от обычных указателей C [3 п.7.8].

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

Для того, чтобы компилятор мог генерировать корректный код, ARC накладывает ограничения на использование методов для управление памятью и toll-free bridging, а также вводит новые описатели свойств и указателей на объекты.

Xcode предоставляет инструмент для конвертации существующего кода в ARC. Некоторые преобразования конвертер выполняет без участия пользователя (например, удаление вызовов retain и release, замена описателей свойств и указателей на объекты), а также помогает исправить проблемы, с которыми не может справиться автоматически. Можно перевести под управление ARC как весь проект целиком, так и отдельные файлы.

Влияет ли ARC на скорость компиляции и работы?

  • никаких накладных расходов GC
  • улучшение производительности: NSObject retain/release в 2.5 раза быстрее, @autoreleasepool в 6 раз быстрее, objc_msgSend на 33% быстрее, retain/autorelease returns в 20 раз быстрее

Компилятор эффективно устраняет множество ненужных retain/release вызовов и ускоряет Objective-C runtime в целом. В частности, общепринятый “return a retain/autoreleased object” паттерн оказывается намного быстрее и на самом деле не помещает объект в autorelease pool, когда метод вызывается из ARC кода.

Следует помнить, что оптимизатор не работает в общей debug конфигурации, так что retain/release траффика будет больше при -O0, чем при -Os.

ARC работает и в ObjC++ режиме.

Новые правила

При использовании ARC:

  • запрещен явный вызов dealloc, однако разрешена реализация dealloc при необходимости выполнения каких-то действий помимо освобождения instance переменных (например, [self setDelegate:nil]);
  • запрещена реализация и вызов release, retain, autorelease, retainCount;

Есть несколько причин, по которым возникает потребность в кастомных retain и release реализациях:

  • Производительность: реализация retain и release для NSObject стала сейчас намного эффективнее и быстрее, в связи с чем кастомная реализация больше не требуется.
  • Реализация системы кастомных weak указателей: теперь вместо этого следует использовать __weak.
  • Реализация синглтона: теперь реализуется паттерном shared instance. Также можно использовать класс вместо методов экземпляра, что позволяет избежать необходимости выделять объект на всех.
  • по-прежнему можно использовать CFRetain, CFRelease и др. при работе с Core Foundation-style объектами в связи с отсутствием возможности полной автоматизации управления объектами CF; [3 п.3.3.2]
  • запрещено использование NSAllocateObject и NSDeallocateObject, объекты создаются с использованием alloc;
  • что касается блоков, то при возврате через return (up the stack) нет необходимости вызывать Block copy. Однако при передаче в качестве параметра (down the stack), например, в arrayWithObjects: и других методах, осуществляющих retain, все еще необходимо использовать [^{} copy]. Следует помнить, что NSString * __block myString является retained в ARC режиме (не является висящим указателем). Чтобы получить поведение, как при ручном управлении памятью, нужно использовать __block NSString * __unsafe_unretained myString или
    лучше __block NSString * __weak myString;
  • запрещено использовать object pointers в структурах C [4 п.4.3.5 – комментарии и объяснения]

Приведенный ниже код не компилируется:
struct X { id x; float y; };

Причина: по умолчанию x является strong, и компилятор не может безопасно синтезировать весь код, необходимый для корректной работы. Например, если вы передаете указатель такой структуре через код, заканчивающийся вызовом free, каждый id должен быть освобожден до того, как будет вызван free для struct. Компилятор не может сделать это надежно, так что strong id в структурах полностью запрещены в ARC режиме. Но есть несколько возможных решений:

  • использовать Objective-C объекты вместо структур (это лучшее решение)
  • если использование Objective-C объектов неоптимально, то рекомендуется использовать void*. При этом требуется использование явных приведений
  • помечать ссылки на объекты __unsafe_unretained. Такой подход может быть полезен в шаблонах следующего типа:
    struct x { NSString *S; int X; } StaticArray[] =
    {
    @"first", 1,
    @"second, 2,
    ...
    };

    Структура объявляется так:
    struct x { NSString * __unsafe_unretained S; int X; }
    Такой подход небезопасен, но для объектов, существующих все время жизни приложения (например, для строковых литералов), может быть очень полезен.

  • для id и void* теперь нужно использовать специальные приведения, которые даюткомпилятору информацию о времени жизни объектов (необходимость возникает при приведении Objective-C объектов и Core Foundation типов);
  • нельзя использовать NSAutoreleasePool объекты; взамен ARC предоставляет блоки @autoreleasepool, использование которых более эффективно; [3 п.7.2 дополнения]
  • нельзя использовать memory zones; в принципе, нет необходимости использовать NSZone, так как modern Objective-C runtime все равно игнорирует их;
  • для обеспечения совместимости с MRR накладываются ограничения на именования методов: например, нельзя начинать имя аксессора с “new”, а следовательно, давать такие имена свойствам, не переопределяя геттеры;
    // Не работает:
    @property NSString *newName;
    // Работает:
    @property (getter=theNewName) NSString *newName;
  • требуется, чтобы результат [super init] присваивался self в методах init;
  • “assigned” переменные экземпляра становятся strong

До ARC, переменные экземпляра были non-owning ссылками – непосредственное присваивание объекта instance переменной не увеличивало время жизни переменной.

Для того, чтобы сделать свойство strong, обычно реализовывался аксессор метод, вызывающий соответствующий метод управления памятью. Напротив, для поддержки weak свойств, аксессор мог определяться так:

@interface MyClass : Superclass
{
id thing; // Weak reference.
}
// ...
@end
@implementation MyClass
- (id)thing
{
return thing;
}
- (void)setThing:(id)newThing
{
thing = newThing;
}
// ...
@end

В ARC instance переменные по умолчанию являются strong ссылками – присваивание объекта instance переменной увеличивает время жизни объекта.

Для поддержания прежнего поведения, необходимо пометить instance переменную как weak или использовать объявление свойства.

@interface MyClass : Superclass
{
id __weak thing;
}
// ...
@end
@implementation MyClass
- (id)thing
{
return thing;
}
- (void)setThing:(id)newThing
{
thing = newThing;
}
// ...
@end

Или:

@interface MyClass : Superclass
@property (weak) id thing;
// ...
@end
@implementation MyClass
@synthesize thing;
// ...
@end


Литература:

  1. www.llvm.org
  2. developer.apple.com/library/ios/#releasenotes/ObjectiveC/RNTransitioningToARC/Introduction/Introduction.html
  3. clang.llvm.org/docs/AutomaticReferenceCounting.html
  4. developer.apple.com/library/ios/#documentation/CompilerTools/Conceptual/
    LLVMCompilerOverview/_index.html

Comments

comments


© 2001-2018 Энтерра Софт - Разработка программного обеспечения на заказ.

Entries (RSS).