Решение проблемы с циклическими ссылками в блоках ObjC

    О блоках в ObjC и правильной работе с ними написано очень много, в том числе и на хабре. Вопрос о том, как правильно работать с self в блоках, чтобы избежать циклических ссылок, регулярно задается на собеседованиях. При использовании таких фреймворков, как ReactiveCocoa количество блоков в коде сильно возрастает, при этом увеличивается шанс допустить ошибку и потерять в памяти объекты. Про попытку окончательно решить эту проблему, метапрограммирование для с99 с экстеншнами и блоками + хипстерсткие макросы с @ под катом.

    Рассмотрим проблему и способы ее решения эволюционно.
    self.block = ^{ 
        [self f1];
        [self f2]; 
    };
    

    Этот код очевидно содержит проблему. Без зануления self.block объект никогда не сможет быть удален, поскольку блок ссылается на self. При включенном LANG_WARN_OBJC_IMPLICIT_RETAIN_SELF компилятор даже выдаст предупреждение.

    Улучшение 1:

    __weak __typeof(self)weakSelf = self;
    self.block = ^{ 
        [weakSelf m1];
        [weakSelf m2]; 
    };
    

    Проблема циклической ссылки решена, но возникает другая. На момент вызова блока объект weakSelf либо существует, либо уже нет. Если объект уже не существует, weakSelf == nil, m1 и m2 не вызовутся — казалось бы, все в порядке. Однако, может получиться так, что на момент вызова m1 объект еще существует, а на момент вызова m2 уже нет. При этом m1 вызовется, а m2 нет — такое поведение может быть неожиданным и неправильным. Это может произойти как при race condition в многопоточном приложении, либо если m1 уменьшает количество ссылок на объект (например, удаляет объект из какой-нибудь коллекции). В случае включенных CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK и CLANG_WARN_OBJC_RECEIVER_WEAK компилятор выдает предупреждение для этого случая.

    Улучшение 2:

    __weak typeof(self)weakSelf = self;
    self.block = ^{
        __strong typeof(self)strongSelf = weakSelf;
        [strongSelf m1];
        [strongSelf m2];
    };
    

    Проблема с консистентностью вызовов методов внутри блока решена. Но обнаруживается новая:
    __weak typeof(self)weakSelf = self;
    self.block = ^{
        __strong typeof(self)strongSelf = weakSelf;
        [strongSelf m1];
        [strongSelf m2];
        NSAssert(foo == bar, @"Cool assert!")
    };
    

    Макросы, такие как NSAssert и RACObserve, неявно используют self, и проблема с циклической ссылкой возвращается.

    Улучшение 3:

    __weak typeof(self)weakSelf = self;
    self.block = ^{
        __strong typeof(self)self = weakSelf;
        [self m1];
        [self m2];
        NSAssert(foo == bar, @"Cool assert!")
    };
    

    Теперь проблема с макросами использующими self решена, но при включенном GCC_WARN_SHADOW компилятор выдает предупрежнение.

    Улучшение 4:

    В библиотеке libextobjc есть макросы @weakify и @stongify которые убирают предупреждение компилятора и немного упрощают код.
    @weakify(self); // self теперь новая локальная переменная с  __weak
    self.block = ^{
        @strongify(self); // self теперь новая локальная переменная с __strong
        [self m1];
        [self m2];
        NSAssert(foo == bar, @"Cool assert!")
    };
    

    Это почти оптимальное решение, но оно все еще не лишено нескольких недостатков: нужно не забыть поставить в нужные места @weakify и @strongify; использование self после @weakify безопасно, но компилятор может выдавать предупрежнение.
    При этом все еще остается вероятность случайно захватить в блоке self по сильной ссылке:
    @weakify(self); // self теперь новая локальная переменная с  __weak
    self.block = ^{
        @strongify(self); // self теперь новая локальная переменная с __strong
        [self m];
        NSLog(@"Ivar value form object: %@", _ivar); // Сильная ссылка на self сохраняется неявно для доступа к  _ivar
        NSAssert(foo == bar, @"Cool assert!")
    };
    

    Для того, что бы этого избежать нужно либо использовать только доступ через property (self.ivar), либо явно использовать переопределенный self:
    @weakify(self); // self теперь новая локальная переменная с  __weak
    self.block = ^{
        @strongify(self); // self теперь новая локальная переменная с __strong
        [self m];
        NSLog(@"Ivar value form object: %@", self->_ivar); // Явно используем свой переопределенный self для доступа к _ivar
        NSAssert(foo == bar, @"Cool assert!")
    };
    

    При этом нужно помнить, что self может быть nil, и явное разыменование self->_ivar вызовет креш.

    С учетом всех этих проблем возникла идея написать макрос, который будет модифицировать не self, а сам блок таким образом, что:
    • self вне scope блока изменяться не должен, как в случае @weakify
    • внутри блока self должен называться self, чтобы избежать неожиданностей с NSAssert и другими макросами
    • до момента вызова блока объект, на который указывает self, хранится по слабой ссылке, а во время вызова блока — по сильной
    • по возможности макрос должен помогать находить блоки, которые неявно захватили self через _ivar
    • все проверки типов должны работать так же, как и без макроса
    • минимизировать изменения в коде при использовании этого макроса
    • overhead в runtime должен быть минимальный

    Макрос должен работать примерно как функция-декоратор в Python, принимать на вход блок и заворачивать его в новый блок-обертку совместимый по параметрам и возвращаемому значению. Для примера рассмотрим блок:
    self.block = ^(NSObject *obj) {
        NSLog(@"%@ %@", [self description], obj);
        return 0;
    };
    

    Начнем модифицировать блок таким образом, чтобы self захватывался как слабая ссылка, по аналогии с кодом из «Улучшение 1». Для этого нам нужен новый scope в котором эта локальная ссылка будет объявлена. В качестве такого scope подойдет анонимный блок, который вызывается сразу после создания:
    self.block = ^{
        __weak typeof(self) weakSelf = self;
        return ^(NSObject *obj) {
            NSLog(@"%@ %@", [weakSelf description], obj);
            return 0;
        };
    }();
    

    Компилятор автоматически выведет тип возвращаемого значения для внешнего безымянного блока, все остается типобезопасным.

    Теперь нужно каким-то образом сделать так, что бы в момент вызова внутри тела внутреннего блока self становился сильной ссылкой. Для этого придется разделить блок на 2 части: декларацию типа ^(NSObject *obj) и, собственно, само тело в {… }. Превратим тело нашего блока в блок без параметров и поместим его вызов в еще один блок, созданный с использованием декларации типа, который превратит self в сильную ссылку:
    self.block = ^{
        __weak typeof(self) weakSelf = self;
        return ^(NSObject *obj) {
            __strong typeof(self)self = weakSelf;
            return ^ (void) {
                NSLog(@"%@, %@", [self description], obj);
                return 0;
            }();
        };
    }();
    

    Основной трюк — это замена исходного блока, эквивалентным ему, но который неявно захватывает weakSelf вместо self, а в момент вызова превращает его в strongSelf.
    return ^(NSObject *obj) {
        __weak typeof(self)self = weakSelf;
        return ^ (void) {
            NSLog(@"%@, %@", [self description], obj);
            return 0;
        }();
    };
    

    по сути то же самое, что и
    ^(NSObject *obj) {
        NSLog(@"%@ %@", [self description], obj);
        return 0;
    };
    

    Итого вместо одного блока создается три. Поскольку самый внешний блок вызывается сразу после создания, от него можно избавиться воспользовавшись code block evaluation aka statement expressions extension:
    self.block = ({
        __weak typeof(self) weakSelf = self;
        ^(NSObject *obj) {
            __strong typeof(self)self = weakSelf;
            return ^ (void) {
                NSLog(@"%@, %@", [self description], obj);
                return 0;
            }();
        };
    });
    

    Осталось завернуть весь boilerplate в макрос, чтобы этим трюком было удобно пользоваться. Если оставить только общий код, то получится:
     ({
        __weak typeof(self) weakSelf = self;
        /* ТИП БЛОКА */ {
            __strong typeof(self)self = weakSelf;
            return ^ (void) {
            /* ТЕЛО БЛОКА */ 
            } ();
        };
    })
    

    Первой идеей было сделать макрос с двумя параметрами, для типа и тела, который бы вызывался так:
    self.block = weakself(^(NSObject *obj), {
        NSLog(@"%@ %@", [self description], obj);
        return 0;
    });
    

    но, к сожалению, при препроцессинге макросы разворачиваются в одну строку, и, как следствие, нельзя поставить breakpoint на произвольную строчку в теле блока. Поэтому пришлось делать так:
    self.block = weakself(^(NSObject *obj)) {
        NSLog(@"%@ %@", [self description], obj);
        return 0;
    } weakselfend ;
    

    такой вариант эквивалентен @weakify/ @strongify из «Улучшение 4». Код макроса:
    #define weakself(ARGS) \
    ({  __weak typeof(self) _private_weakSelf = self; \
        ARGS { \
            __strong typeof(_private_weakSelf) self __attribute__((unused)) = _private_weakSelf; \
            return ^ (void) {
    
    #define weakselfend } (); }; })
    

    Одной из целей при создании макроса было обезопасить себя от неявного захвата self при доступе к ivar. К сожалению, как сделать это в compile time я так и не придумал. Единственный вариант — это assert/log для дебаг версии при создании блока (достаточно просто создать блок чтобы проверка сработала, не обязательно его вызывать). Тут стоит немного напомнить о том, как работает memory management для блоков и объектов, которые они захватывают. Существуют 3 типа блоков:
    • NSGlobalBlock — блоки, созданные на верхнем уровне файла с исходным кодом, по сути аналогичны функциям с точки зрения memory management, переменные в scope не захватывают по этому интереса для нас не представляют.
    • NSStackBlock — начальный тип для всех остальных созданных блоков, создаются на стеке, не увеличивают счетчики ссылок у объектов, которые захватывают, поскольку время жизни такого блока меньше либо равно времени жизни переменных из его лексического scope.
    • NSMallocBlock — это NSStackBlock который был перенесен в heap явным вызовом copy/Block_copy или неявно компилятором. Один из случаев когда компилятор неявно вставляет Block_copy — это возврат блока как результат из функции/блока. В момент превращения NSStackBlock в NSMallocBlock и происходит увеличения счетчиков ссылок объектам, которые блок захватил в свой scope.

    Таким образом, для того, что бы проверить, захватывает ли блок сильную ссылку на self нужно сравнить счетчик ссылок на self, до того как блок был перенесен в heap, и после. Если счетчик увеличился, значит блок захватил self по сильной ссылке. Эта проверка не может быть надежной в 100% случаев, поскольку счетчик ссылок на self может изменяться из других потоков во время переноса блока в heap, однако в нормальной программе эта ситуация маловероятна, и для Debug-сборки вполне подходит.

    Для получения счетчика ссылок у объекта раньше можно было использовать метод retainCount, однако с ARC он больше не доступен, но CFGetRetainCount по-прежнему работает через toll-free bridging. Осталось только вставить вызовы этой функции с параметром self в нужные места и сравнить результаты.
    self.block = {(
        __weak typeof(self) weakSelf = self;
        // Первый раз счетчик ссылок для self нужно получить здесь
        ^(NSObject *obj) {
            __strong typeof(self)self = weakSelf;
            return ^ (void) {
                NSLog(@"%@, %@", [self description], obj);
                return 0;
            }();
        };
    })  // второй раз здесь и сравнить. Но у нас нет доступа к переменным из statement expression
    

    Проблема в том, что результат statement expressions — это последняя строчка в нем. Поведение аналогично анонимному блоку, который вызывается сразу после объявления. Поскольку последняя строчка statement expression это декларация блока, то для того, чтобы этот блок оставался валидным, компилятор перенесет его в heap. Получается, мы можем сохранить вызов CFGetRetainCount для self в локальную переменную внутри statement expression, а второй вызов CFGetRetainCount нам нужно делать после последней строчки statement expression. Если бы речь шла про C++, мы бы могли создать объект на стеке, а в деструкторе объекта сделать все, что нам нужно, поскольку деструтор бы вызвался после выполнения последней строчки statement expression. К счастью, clang поддерживает gcc-extension который позволяет выставить cleanup-функцию (аналог деструктора) для любой переменной на стеке, которая будет вызвана в тот момент, когда переменая уйдет из области видимости. Через этот extension работает макрос @onExit из libextobjc.

    Для реализации проверки счетчика ссылок понадобится дополнительная структура:
    struct RefCountCheckerData {
        CFTypeRef weakSelf;
        NSUInteger refCountBefore;
    };
    

    И функция, которая будет выставлена как cleanup.
    static inline void vbr_CheckRefCountForWeakSelf(struct RefCountCheckerData *data) {
        const NSInteger refCountAfter = CFGetRetainCount(data->weakSelf);
        const NSInteger countOfSelfRefInBlock = refCountAfter - data->refCountBefore;
        if (countOfSelfRefInBlock > 0) {
            raise(SIGPIPE);
        }
    }
    

    Создаем структуру на стеке, выставляем cleanup функцию и инициализируем указатель на weakSelf и число ссылок на него. Cleanup функция вызовется когда переменная _private_refCountCheckerData уйдет из области видимости, а в этот момент наш блок уже в heap.
    self.block = {(
        __weak typeof(self) weakSelf = self;
        __attribute__((cleanup(vbr_CheckRefCountForWeakSelf), unused))
            struct RefCountCheckerData _private_refCountCheckerData = {
                .weakSelf = (__bridge CFTypeRef)self,
                .refCountBefore = CFGetRetainCount((__bridge CFTypeRef)self),
            };
        ^(NSObject *obj) {
            __strong typeof(self)self = weakSelf;
            return ^ (void) {
                NSLog(@"%@, %@", [self description], obj);
                return 0;
            }();
        };
    });
    

    С такой версией макроса сработает breakpoint в отладчике при попытке получить доступ к ivar не через self, например таком self.block = ^{ NSLog(@"%d", _ivarInteger); };

    Перед тем как, представить финальный вариант макроса, нужно привести его в современный хипстерский вид. Для ObjC модно делать макросы начинающиеся, как и ключевые слова языка, с @, например: @strongify, @onExit. Но препроцессор не разрешает использовать @ как часть имени макроса. В extobjc для этого используют вставку в начало макроса autoreleasepool {} либо try {} catch (...) {}, символ @ таким образом приклеивается либо к try либо к autoreleasepool. После разворачивания макроса в коде появляется ненужный пустой autoreleasepool либо try catch, но это никого сильно не волнует. Однако такой подход не работает для макроса weakself, потому что результат weakself это выражение, а выражение не может содержать @autoreleasepool try {} catch (...) {} в начале.
    self.block = @weakself(^(NSObject *obj)) {
        NSLog(@"%@ %@", [self description], obj);
        return 0;
    } @weakselfend ;
    

    Когда речь идет о сложных выражениях в С на ум первым делом приходит тернарный оператор. Осталось понять, как его применить. Первым в голову пришло записать как-то так: self.block = @1? /* здесь код блоков */: nil;

    Для этого нужно всего лишь добавить 1? в начало weakself и :nil; в конец weakselfend. Но self.block = 1? /* здесь код блоков */: nil; вполне корректное выражение, поэтому @weakself и weakself будут работать.

    Вариант self.block = @[]? /* здесь код блоков */: nil; не дает использовать @weakself без @, однако после проверки дизассемблера выяснилось, что оптимизатор не выбрасывает создание пустого массива, а это лишний overhead в runtime.

    Наконец в голову пришла идея использовать особенности String Literal Concatenation в ObjC.
    const char *s0 = "ABC" "DEF"; // это валидная C-строка "ABCDEF"
    NSString *s1 = @"ABC" @"DEF"; // это валидная ObjC-строка @"ABCDEF"
    NSString *s2 = @"ABC" "DEF"; // это тоже валидная ObjC-строка @"ABCDEF"
    NSString *s3 = "ABC" @"DEF"; // а это ошибка компиляции
    

    Итак, финальный вариант макроса:
    #define weakself(ARGS) \
    "weakself should be called as @weakself" @"" ? \
    ({  __weak typeof(self) _private_weakSelf = self; \
        ARGS { \
            __strong typeof(_private_weakSelf) self __attribute__((unused)) = _private_weakSelf; \
            return ^ (void) {
    
    #define weakselfnotnil(ARGS) \
    "weakself should be called as @weakself" @"" ? \
    ({  __weak typeof(self) _private_weakSelf = self; \
        ARGS { \
            __strong typeof(_private_weakSelf) self __attribute__((unused)) = _private_weakSelf; \
            return ^ (void) { if (self)
    
    #define weakselfend \
        try {} @finally {} } (); }; \
    }) : nil
    

    @weakselfnotnil отличается тем, что если к моменту вызова блока self уже удален, то блок не вызовется. Подходит только для случаев, когда блок не имеет возвращаемого значения, иначе не понятно, что возвращать в случае если self уже удален. Сделан в основном для безопасного использования ivar через явное разыменованиее self:
    self.block = @weakselfnotnil(^) {
        NSLog(@"%d", self->_ivar);
    } @weakselfend;
    


    Производительность


    Сильно беспокоиться из-за производительности тут, пожалуй, не стоит, накладных расходов должно быть не много. Трюк для добавления @ в начало макроса полностью выбрасывается оптимизатором. С накладными расходами на вызов дополнительного блока дела обстоят интереснее. Для проверки как обстоят дела с накладными расходами рассмотрим 2 случая, с использованием макросов из libextobjc и нашего weakself:
    - (void)m1 {
        @weakify(self);
        self.block = ^(NSObject * obj) {
            @strongify(self);
            NSLog(@"%@", [self description]);
            return 0;
        };
    }
    
    - (void)m2 {
        self.block = @weakself(^(NSObject * obj)) {
            NSLog(@"%@", [self description]);
            return 0;
        } @weakselfend;
    }
    

    Собираем с -O3, открываем в Hooper и смотрим псевдокод для обоих случаев
    function -[ViewController m1] {
        asm{ vst1.64    {d8, d9, d10, d11}, [r4:128]! };
        asm{ vst1.64    {d12, d13, d14, d15}, [r4:128] };
        r1 = *_NSConcreteStackBlock;
        *((sp - 0x40 & !0xf) - 0x50) = r1;
        var_4 = 0xc2000000;
        var_24 = ((sp - 0x40 & !0xf) - 0x50) + 0x14;
        asm{ stm.w      r5, {r1, r2, r3} };
        r5 = [r0 retain];
        objc_initWeak(var_24, r5);
        [r5 release];
        r0 = *__objc_personality_v0;
        r1 = *0xac24;
        var_52 = r0;
        var_56 = GCC_except_table0;
        var_60 = &var_12;
        var_68 = (sp - 0x40 & !0xf) - 0x50;
        var_64 = (r1 | 0x1) + 0xabc4;
        var_32 = 0x1;
        [r5 setBlock1:(sp - 0x40 & !0xf) - 0x50];
        objc_destroyWeak(var_24);
        r0 = _Unwind_SjLj_Unregister(&var_28);
        asm{ vld1.64    {d8, d9, d10, d11}, [r4:128]! };
        asm{ vld1.64    {d12, d13, d14, d15}, [r4:128] };
        Pop();
        Pop();
        Pop();
        return r0;
    }
    
    function ___20-[ViewController m1]_block_invoke {
        r4 = objc_loadWeakRetained(r0 + 0x14);
        r0 = [r4 description];
        r5 = [r0 retain];
        NSLog(@"%@", r5);
        [r5 release];
        [r4 release];
        return 0x0;
    }
    
    function -[ViewController m2] {
        r4 = r0;
        r0 = *_NSConcreteStackBlock;
        *(sp - 0x18) = r0;
        var_4 = 0xc2000000;
        asm{ stm.w      r3, {r0, r1, r2} };
        objc_initWeak((sp - 0x18) + 0x14, r4);
        r5 = objc_retainBlock(sp - 0x18);
        objc_destroyWeak((sp - 0x18) + 0x14);
        [r4 setBlock1:r5];
        r0 = [r5 release];
        return r0;
    }
    
    function ___20-[ViewController m2]_block_invoke {
        r4 = objc_loadWeakRetained(r0 + 0x14);
        r0 = [r4 description];
        r5 = [r0 retain];
        NSLog(@"%@", r5);
        [r5 release];
        [r4 release];
        return 0x0;
    }
    


    Получается, что weakself эффективнее чем @weakify/strongify, внутренний дополнительный блок полностью заинлайнился и _block_invoke в обоих случаях выглядит одинаково. Но способ которым в extobjc «съедают» @ в начале макроса добавляет бесполезный код по обработке исключений в рантайме, что видно по _Unwind_SjLj_Unregister.
    В случае компиляции с -Os все не так хорошо, блок не инлайнится и вместо одного _block_invoke генерируется два
    function ___20-[ViewController m2]_block_invoke {
        r0 = objc_loadWeakRetained(r0 + 0x14);
        r1 = *_NSConcreteStackBlock;
        *(sp - 0x18) = r1;
        var_4 = 0xc2000000;
        asm{ stm.w      r4, {r1, r2, r3} };
        var_20 = r0;
        r4 = [r0 retain];
        r5 = ___20-[ViewController m2]_block_invoke_2(sp - 0x18);
        [var_20 release];
        [r4 release];
        r0 = r5;
        return r0;
    }
    
    function ___20-[ViewController m2]_block_invoke_2 {
        r0 = *(r0 + 0x14);
        r0 = [r0 description];
        r4 = [r0 retain];
        NSLog(@"%@", r4);
        [r4 release];
        return 0x0;
    }
    


    К сожалению, clang пока не позволяет добавить атрибут always_inline к блоку.
    Полный исходный код и autocomplete для Xcode тут.
    Viber
    Company
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 16

      –3
      Автор спасибо! Статья очень полезна, и не только новичкам. Спасибо огромное! Но, ёпрст, Objective-C, а не ObjC. У меня как у профессионального программиста на этом языке и препода на ту же тему, кровоточат глаза, когда я читаю такие наименования.
        +5
        Боюсь что языки и технологии с названиями длиннее пары слогов просто обречены на сокращение: javascript -> js, ruby on rails -> rails, visual basic .net -> vb.net, assembly -> asm, gnu emacs lisp -> elisp и т. д.
          +1
          Что поделать с перфекционизмом? :-)
            0
            Перейти на чистый С
          +1
          Использование objc в header'ах и документации к Clang'у не смущает?
          +2
          Автор, спасибо! Использую каждый день ;)
            +1
            Огромное спасибо за статью! Объемлющая и глубокая.
            Если я правильно понял, то этот подход употребим для практически каждого блока в коде, соответственно возникает вопрос:
            В качестве вариантов, которые приходят в голову
            -Добавление скрипта во время процессинга .m файлов, который будет по маркеру или просто так обнаруживать блоки и преобразовывать их в нужный вид — тогда можно будет обойтись одним макросом, без замыкающего weakself_end.
            -Добавление сниппета/скрипта автоматора c примерно похожим поведением, но явного — который выделенный блок приведет к нужной вам форме?
              +3
              В общем случае не в каждом блоке нужно захватывать self по слабой ссылке, иногда нужно наоборот по сильной. И не все блоки создаются в контексте метода, поэтому self не всегда существует. К тому же @weaksekf блок будет обязательно перенесен в heap, а это лишний overhead когда подходит и стековый (например enumerateObjectsUsingBlock:). Поэтому все блоки заворачивать в этот макрос я не рекомендую. Если добавить файлы автокомплита в Xcode, то после набранных символов @we дополняется заглушка сразу с @weakselfend. Я не сторонник дополнительного препроцессинга перед сборкой, а в коде существующие блоки меняли на новые простым макросом в vim.
              0
              Я очень люблю простые решения понятные большинству программистов разного уровня и потому для себя решаю эту проблему следующим образом:

              __weak MyClass *weakSelf = self;
              self.block = ^{
              	[weakSelf onlyOneMethodCall];
              };
              
              - (void)onlyOneMethodCall
              {
              	[self m1];
              	[self m2];
              	NSAssert(foo == bar, @"Cool assert!");
              }
              
              

              Дело в том, что ARC гарантирует, что self будет жить пока не завершится вызов метода, потому вызываем 1 метод из блока и все остальное внутри этого метода. Если надо использовать переменные окружения — я их передаю в метод как параметры.
                0
                Так это тоже самое, что и старые добрые делегаты, или даже скорее target-selector, только с ненужной прослойкой из блока.
                  0
                  Делегирование это паттерн передачи полномочий и никак не зависит от метода его реализации тоже касается и концепта target-selector. Данный кусок кода как раз таки решает исключительно проблему циклических ссылок. Я не говорю, что 2 + 2 надо выносить в отдельный метод, но если и так наглядно, что код внутри блока сложен — почему бы не вынести код в отдельный метод? Каждая дополнительная инструкция введенная в ваш проект (вроде @weakify) усложнит задачу входа следующих сотрудников.
                    0
                    В случае с weakSelf, если в блоке более двух его использований, придется выносить отдельный метод, а это большая часть блоков. Конечно использование чего-то, за рамками базового языка и API усложняет задачу входа новых сотрудников, зато сильно упрощает жизнь текущим сотрудникам. Тут баланс надо подбирать в зависимости от ситуации.
                  0
                  >Дело в том, что ARC гарантирует, что self будет жить пока не завершится вызов метода
                  Это неправда.
                0
                На блоки опираться в том случае лучше, объект если знает о времени своей кончины или одноразовым является. Смело он может лямбдические свойства обнулить перед смертью своей, циклы разорвав нерушимые. В том случае же, когда жизни окончание объекта неведомо самим объектом, то коллбека выполнение в виде делегата лучше будет. Ссылку он имеет слабую, и тем самым цикл создать не может.

                Ваш очевидности магистр.

                (за решение спасибо, не додумался бы в макрос обернуть)
                  0
                  с RAC все сложнее получается. Да и не все API предоставляют альтернативу в виде делегатов.

                Only users with full accounts can post comments. Log in, please.