2013-02-12 14 views
16

Per meglio illustrare la questione, si consideri la seguente forma semplificata del blocco ricorsione:Block ricorsione e rompere mantenere ciclo

__block void (^next)(int) = ^(int index) { 
    if (index == 3) { 
     return; 
    } 
    int i = index; 
    next(++i); 
}; 
next(0); 

XCode (ARC-abilitato) avverte che "Capturing 'prossimo' fortemente in questo blocco è probabile che porti a un ciclo di conservazione ".

Concordato.

Domanda 1: Sarebbe il mantenere ciclo di essere rotto con successo impostando il blocco stesso a nil, in questo modo:

__block void (^next)(int) = ^(int index) { 
    if (index == 3) { 
     next = nil; // break the retain cycle 
     return; 
    } 
    int i = index; 
    next(++i); 
}; 
next(0); 

(Nota: si sarebbe ancora ottenere il stesso avviso, ma forse è ingiustificato)

Domanda 2: quale sarebbe una migliore implementazione della ricorsione a blocchi?

Grazie.

+0

Perché è stato dichiarato __block comunque? Non è catturato. –

+0

@Catfish_Man, è '__block int i' perché è stato modificato nella riga successiva del codice. – krisk

+0

Questo codice non potrebbe funzionare; next non è assegnato fino a quando il blocco non viene compilato. Pertanto, la chiamata al prossimo all'interno del blocco si bloccherà. –

risposta

4

Per eseguire l'esecuzione del blocco ricorsivo senza ritenzione di ciclo, è necessario utilizzare due riferimenti di blocco: uno debole e uno forte. Quindi, per il vostro caso, questo è ciò che il codice potrebbe essere simile:

__block __weak void (^weak_next)(int); 
void (^next)(int); 
weak_next = next = ^(int index) { 
    if (index == 3) { 
    return; 
    } 
    int i = index; 
    weak_next(++i); 
}; 
next(0); 

Si noti che il blocco di cattura il riferimento di blocco debole (weak_next), e il contesto esterno cattura il forte richiamo (successivo) per mantenere il blocco intorno . Entrambi i riferimenti puntano allo stesso blocco.

Vedere https://stackoverflow.com/a/19905407/1956124 per un altro esempio di questo modello, che utilizza anche la ricorsione di blocco. Inoltre, la discussione nella sezione commenti del seguente articolo è rilevante anche qui: http://ddeville.me/2011/10/recursive-blocks-objc/

1

Penso che @newacct sia corretto sulla soluzione di @Matt Wilding; sembra che nulla in questo caso avrà un forte riferimento al blocco successivo e provocherà un'eccezione durante l'esecuzione (almeno lo ha fatto per me).

Non so quanto sia comune trovare i blocchi ricorsivamente in natura in objc. Tuttavia, in un'implementazione reale (se effettivamente richiesto) su diciamo, un controller della vista, si potrebbe definire il blocco e quindi impostare una proprietà di interfaccia interna con un forte riferimento a detto blocco:

typedef void(^PushButtonBlock)(); 

@interface ViewController() 
@property (strong, nonatomic) PushButtonBlock pushButton; 
@end 

@implementation ViewController 
    ... 
    // (in viewDidLoad or some such) 
    __weak ViewController *weakSelf = self; 

    self.pushButton = ^() { 
    [weakSelf.button pushIt]; 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), weakSelf.pushButton); 
    }; 

    self.pushButton(); 
    ... 
@end 

Questo funziona benissimo per me e non ha avvisi del compilatore sui cicli di mantenimento (e nessuna perdita negli strumenti). Ma, penso che probabilmente eviterò di farlo (chiamate ricorsive a blocchi) nella maggior parte dei casi in objc - è puzzolente. Ma interessante in ogni caso.