2011-01-28 3 views

ответ

27

Веселая история! Блоки фактически являются объектами Objective-C. Тем не менее, нет открытого API для получения указателя блоков self.

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

__weak __block int (^block_self)(int); 
int (^fibonacci)(int) = [^(int n) { 
    if (n < 2) { return 1; } 
    return block_self(n - 1) + block_self(n - 2); 
} copy]; 

block_self = fibonacci; 

Это необходимо применить модификатор __block к block_self, потому что в противном случае, block_self ссылка внутри fibonacci будет ссылаться на него перед ним (сбой вашей программы при первом рекурсивном вызове). __weak должен обеспечить, чтобы блок не фиксировал значительную ссылку на себя, что могло бы вызвать утечку памяти.

+1

Отличный ответ. модификатор __block заставил бы меня некоторое время подумать самостоятельно. Благодаря тонну! Rep вам! –

+3

Если вы передадите его функции, которая завершит ее копирование (например, 'dispatch_async()'), вам также потребуется скопировать блок в область назначения. –

+2

Этот код будет протекать, даже если у вас есть условие завершения. Добавьте переменную обратного отсчета и запустите ее с помощью проверочной проверки инструментов.Кроме того, компилятор выдает следующее предупреждение, если у вас есть правильные предупреждения: «Захват« myBlock »в этом блоке, вероятно, приведет к циклу сохранения« – Karl

6

Вы должны объявить переменную блок, как __block:

typedef void (^MyBlock)(id); 

__block MyBlock block = ^(id param) { 
    NSLog(@"%@", param); 
    block(param); 
}; 
+0

@Joe Blow ['__block' конкретно это ключевое слово] (http://developer.apple.com/library/ios/documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html#// apple_ref/DOC/UID/TP40007502-CH6-SW6). –

+0

Этот код также будет протекать по той же причине, что и код zneak. – Karl

14

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

typedef void (^CountdownBlock)(int currentValue); 

- (CountdownBlock) makeRecursiveBlock 
{ 
    CountdownBlock aBlock; 
    __block __unsafe_unretained CountdownBlock aBlock_recursive; 
    aBlock_recursive = aBlock = [^(int currentValue) 
    { 
     if(currentValue >= 0) 
     { 
      NSLog(@"Current value = %d", currentValue); 
      aBlock_recursive(currentValue-1); 
     } 
    } copy]; 
#if !__has_feature(objc_arc) 
    [aBlock autorelease]; 
#endif 

    return aBlock; 
} 

- (void) callRecursiveBlock 
{ 
    CountdownBlock aBlock = [self makeRecursiveBlock]; 

    // You don't need to dispatch; I'm doing this to demonstrate 
    // calling from beyond the current autorelease pool. 
    dispatch_async(dispatch_get_main_queue(),^
        { 
         aBlock(10); 
        }); 
} 

Важные соображения:

  • You необходимо скопировать блок в кучу вручную или попытаться получить доступ к несуществующему стекю, когда вы вызываете его из другого контекста (ARC обычно делает это для вас, но не во всех случаях. Лучше играть в нее безопасно).
  • Вам нужны ДВОЙНЫЕ ссылки: один, чтобы удерживать сильную ссылку на блок, и один, чтобы удерживать слабую ссылку для рекурсивного блока для вызова (технически это требуется только для ARC).
  • Вы должны использовать квалификатор __block, чтобы блок не фиксировал еще неназначенное значение ссылки на блок.
  • Если вы занимаетесь ручным управлением памятью, вам необходимо автоматически авторизовать скопированный блок.
+0

Это не сработает в ARC, но как насчет '' copy] autorelease] '' вместо того, чтобы просто копировать, так что вам не нужно вызывать выпуск внутри блока? Для ARC возможно выполнение '' 'aBlock = nil''' после вызова' '' aBlock (10); '' '? –

+0

На самом деле, пересматривая это решение, оказывается, что сохранение ссылки внутри блока вызовет утечку, если блок не будет вызван. Теперь я обновил ответ, чтобы он работал правильно, называете ли вы его или нет. – Karl

+0

У меня есть проблема, которая добавляет к этому сложный уровень сложности, но я не совсем уверен, что моя проблема разрешима. Я хотел бы иметь возможность повторять вызовы API через AFNetworking. Я думаю, что этот pastebin упрощает и иллюстрирует проблему. http://pastebin.com/xRPJuckZ Небезопасный рекурсивный блок вызывается изнутри другого блока внутри себя, а его вызов приводит к ошибке плохого доступа. Не могли бы вы представить, как это решить? –

3

Для блоков (пока) нет self. Вы можете построить один такой (предполагается, что ARC):

__block void (__weak ^blockSelf)(void); 
void (^block)(void) = [^{ 
     // Use blockSelf here 
} copy]; 
blockSelf = block; 
    // Use block here 

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

Смежные вопросы