Статьи

Поддержка ARC без ветвления для iOS

Когда Apple объявила о поддержке ARC (Автоматический подсчет ссылок), разработчики iOS подскочили от радости от перспективы того, что больше не придется засорять кодовые базы кодом ручного управления памятью. Наконец, мы можем сосредоточиться на логике без всего подверженного ошибкам и трудоемкого шаблона. Но даже больше, чем большинство новых функций в iOS5, переходный период для ARC был немного болезненным.

Эта проблема

Решение поддержать ARC в вашем проекте однозначно. Да или нет. Большинство разработчиков iOS используют ARC для всех новых проектов, для которых требуется поддержка не менее 4.0. (ARC не поддерживается iOS 3.1.3.) Однако, если вы являетесь автором фреймворка или библиотеки, решение о поддержке ARC не так просто, и есть несколько вариантов, которые вы можете сделать.

Самый простой способ сделать это — вообще не переходить на ARC. Разработчики могут по-прежнему использовать исходный код вашей библиотеки, указав параметр -fno-objc-arc в разделе источников проекта своего проекта .

Пример опции -fno-objc-arc

Большая проблема с этим подходом вообще требует такой опции. Он должен быть указан отдельно для каждого файла, и, если ваша библиотека состоит из множества файлов, таких как Cocos2D , разработчик должен будет установить опцию для всех из них. Интерфейс XCode для этого довольно запутанный, и многие разработчики не знают о параметре или о том, где его указать. Кроме того, если они позже обновляют библиотеку в своем проекте, XCode удаляет эту опцию, и ее необходимо вводить заново. Довольно неприятный и плохое отражение вашего кода.

Другой способ — просто преобразовать вашу библиотеку в ARC-only. Любой, кто не использует ARC для своего проекта, будет иметь утечки памяти и, вероятно, массу предупреждений от своего статического анализатора. Это нереальный вариант прямо сейчас. Когда гораздо больший процент разработчиков iOS будет использовать ARC, это будет. Пока еще слишком много не-ARC проектов, особенно если требуется поддержка iOS 3.1.3.

Поэтому многие разработчики решили добавить поддержку ARC. Обычный способ — добавить ветку в свой проект, который поддерживает ARC. TouchXML , TouchJSON , XMLDocument и Nimbus — это всего лишь небольшая группа проектов, использующих этот подход. SVProgressHUD даже попробовал такую ​​ветку и полностью отказался от нее из-за сложности и нежелательности делать такую ​​глупую вещь!

Я настоятельно отговариваю вас от такого подхода. Поддержание двух веток редко бывает хорошей идеей в реальном мире, не говоря уже о простой библиотеке iOS с открытым исходным кодом. Это добавляет небольшую сложность к захвату вашего исходного кода и неизбежно приведет к некоторым проблемам фиксации.

Есть лучший способ.

Основное решение

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

Для этого есть два метода, и оба используют одну и ту же базовую проверку:

#if __has_feature(objc_arc)
    ...ARC code here...
#else
    ...non-ARC code here...
#endif

__Has_feature (objc_arc) чек не будет компилировать прилагаемый код для любого компилятора , который не поддерживает функцию ARC, ни для компилятора LLVM 3.0 , если ARC не включен. Вы, конечно, можете использовать эту структуру кода и отделить свой код ARC и не-ARC, не читая дальше, но если вы это сделаете, вы можете быстро обнаружить, что ваш код очень быстро станет беспорядочным. Кроме того, XCode исторически выполнял довольно дерьмовую работу по автоматическому выравниванию кода при использовании условной компиляции.

Как мы сохраняем код чистым?

Макро Решение

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

Это лучше всего описать, представив набор макросов, которые вы можете бесплатно добавить в свой собственный проект ( ARCMacros.h ):

//
//  ARCMacros.h
//
//  Created by John Blanco on 1/28/2011.
//  Rapture In Venice releases all rights to this code.  Feel free use and/or copy it openly and freely!
//

#if !defined(__clang__) || __clang_major__ < 3
    #ifndef __bridge
        #define __bridge
    #endif

    #ifndef __bridge_retain
        #define __bridge_retain
    #endif

    #ifndef __bridge_retained
        #define __bridge_retained
    #endif

    #ifndef __autoreleasing
        #define __autoreleasing
    #endif

    #ifndef __strong
        #define __strong
    #endif

    #ifndef __unsafe_unretained
        #define __unsafe_unretained
    #endif

    #ifndef __weak
        #define __weak
    #endif
#endif

#if __has_feature(objc_arc)
    #define SAFE_ARC_PROP_RETAIN strong
    #define SAFE_ARC_RETAIN(x) (x)
    #define SAFE_ARC_RELEASE(x)
    #define SAFE_ARC_AUTORELEASE(x) (x)
    #define SAFE_ARC_BLOCK_COPY(x) (x)
    #define SAFE_ARC_BLOCK_RELEASE(x)
    #define SAFE_ARC_SUPER_DEALLOC()
    #define SAFE_ARC_AUTORELEASE_POOL_START() @autoreleasepool {
    #define SAFE_ARC_AUTORELEASE_POOL_END() }
#else
    #define SAFE_ARC_PROP_RETAIN retain
    #define SAFE_ARC_RETAIN(x) ([(x) retain])
    #define SAFE_ARC_RELEASE(x) ([(x) release])
    #define SAFE_ARC_AUTORELEASE(x) ([(x) autorelease])
    #define SAFE_ARC_BLOCK_COPY(x) (Block_copy(x))
    #define SAFE_ARC_BLOCK_RELEASE(x) (Block_release(x))
    #define SAFE_ARC_SUPER_DEALLOC() ([super dealloc])
    #define SAFE_ARC_AUTORELEASE_POOL_START() NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    #define SAFE_ARC_AUTORELEASE_POOL_END() [pool release];
#endif

Эти макросы поставляются в комплекте с InnerBand . Фактически, проект InnerBand поддерживает ARC и non-ARC, используя те же макросы. Он служит отличной ссылкой на то, как их использовать.

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

Суть в том, что вам не нужно беспокоиться об этом. Просто используйте макросы, как если бы вы программировали по-старому, и обо всем позаботятся за вас. Конечно, всего лишь с несколькими оговорками.

Обратите внимание, что __bridge_tranfer не присутствует. Это не то, что можно просто игнорировать в не-ARC-контексте. Бесплатное соединение является волосатым с ARC. Если вы используете его, я рекомендую придерживаться проверки __has_feature (objc_arc) и применять ARC и non-ARC по отдельности.

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

демонстрация

Теперь я покажу вам, как использовать макросы. Вы можете оставить свой проект как не-ARC или преобразовать его в ARC. Я предпочитаю оставить это как не ARC, но это только я. Вы даже можете создать вторую цель и иметь обе. Как бы вы это ни делали, просто обязательно протестируйте обе скомпилированные версии вашего кода перед выпуском.

Вот как вы выполните авто-релиз:

- (User *)userWithName:(NSString *)name {
    return SAFE_ARC_AUTORELEASE([[User alloc] initWithName:name]);
}

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

myName = SAFE_ARC_AUTORELEASE(myName);

Сохранения и выпуски похожи на автоматическое освобождение:

- (void)setName:(NSString *)name {
    if (name_ != name) {
        SAFE_ARC_RELEASE(name_);
        name_ = SAFE_ARC_RETAIN(name);
    }

    ...

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

Блоки обычно не нужно копировать в ARC. Вот как бы вы справились с этим в своем многоконтекстном коде:

- (id)initWithBlock:(void (^)(void))block
    if ((self = [super init])) {
        block_ = SAFE_ARC_BLOCK_COPY(block);
    }

    return self;
}

- (void)dealloc {
    SAFE_ARC_BLOCK_RELEASE(block_);
    SAFE_ARC_SUPER_DEALLOC();
}

Выше также демонстрируется, как вызывать dealloc для суперкласса, что явно запрещено в ARC. Код удаляется компилятором при использовании ARC, но вызывает [super dealloc] при компиляции как старый код.

Автозапуск бассейнов тоже изменился. Хотя это не является строго необходимым, поддерживаются два разных объявления пула автоматического выпуска. Когда вы пишете код для работы в пуле автоматического выпуска, оберните его следующим образом:

- (id)complexMethodCall {
    SAFE_ARC_AUTORELEASE_POOL_START();

    ...my code here...

    SAFE_ARC_AUTORELEASE_POOL_END();
}

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

@property(nonatomic, SAFE_ARC_PROP_RETAIN) User *user;

Если вы хотите использовать какие-либо специфичные для ARC спецификаторы, кроме этого, вам необходимо использовать компиляцию кода с __has_feature (objc_arc), поскольку не существует не-ARC-эквивалентов.

И, наконец, есть бесплатный звонок. Это одна из областей, которая стала более сложной в ARC.
Я обычно прибегаю к компиляции кода. См. Код InnerBand для примеров этой хирургической процедуры. К счастью, вы не будете использовать это много, если вы не используете Core Graphics.

Прощальные слова

Эти макросы не предлагаются в качестве альтернативы знанию ARC. Напротив, вы должны знать ARC, прежде чем пытаться преобразовать любой старый код. Суть этих макросов в том, чтобы легко и чисто разрешить запуск кода библиотеки в любом контексте. Если вам не очень нравится знание ARC, мои любимые ресурсы для ARC — это документация Apple ARC , Майк Эш и скрытая жемчужина от создателя Kobold2D.


Источник:
http://raptureinvenice.com/arc-support-without-branches/