2011-01-28 14 views

risposta

27

Una storia divertente! I blocchi sono in realtà oggetti Objective-C. Detto questo, non ci sono API esposte per ottenere il puntatore dei blocchi self.

Tuttavia, se si dichiarano blocchi prima di utilizzarli, è possibile utilizzarli in modo ricorsivo. In un ambiente non-garbage collection, si dovrebbe fare qualcosa di simile:

__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; 

È necessaria per applicare il modificatore __block-block_self, perché altrimenti, il riferimento block_self all'interno fibonacci rimanda alla prima che viene assegnato (arresto anomalo del programma alla prima chiamata ricorsiva). Lo __weak garantisce che il blocco non rilevi un riferimento forte a se stesso, il che causerebbe una perdita di memoria.

+1

Ottima risposta. il modificatore __block mi avrebbe richiesto un po 'per capire da solo. Grazie mille! Rep per te! –

+3

Se lo passi a una funzione che finirà per copiarlo (come ad esempio 'dispatch_async()'), dovrai anche copiare il blocco nell'ambito del compito. –

+2

Questo codice perde, anche se si dispone di una condizione di terminazione. Aggiungi una variabile per il conto alla rovescia ed eseguila attraverso il correttore di perdite di Strumenti per vedere.Inoltre, il compilatore emetterà il seguente avvertimento se hai attivato gli avvisi giusti: "Catturare 'myBlock' in questo blocco è probabile che porti a un ciclo di conservazione" – Karl

6

si deve dichiarare la variabile blocco come __block:

typedef void (^MyBlock)(id); 

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

@Joe Blow ['__block' specificamente è una parola chiave] (http://developer.apple.com/library/ios/documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html#// apple_ref/doc/uid/TP40007502-CH6-SW6). –

+0

Questo codice si diffonderà anche per lo stesso motivo del codice zneak. – Karl

14

Il seguente codice di blocco ricorsivo verrà compilare ed eseguire con ARC, GC, o la gestione manuale della memoria, senza andare in crash, che perde, o l'emissione di avvisi (analizzatore o normale):

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); 
        }); 
} 

considerazioni importanti:

  • È deve copiare il blocco sull'heap manualmente altrimenti tenterà di accedere a uno stack inesistente quando lo chiamate da un altro contesto (ARC lo fa di solito per te, ma non in tutti i casi. Meglio giocare sul sicuro).
  • Sono necessari due riferimenti: uno per mantenere il riferimento forte al blocco e uno per contenere un riferimento debole per il blocco ricorsivo da chiamare (tecnicamente, questo è necessario solo per ARC).
  • È necessario utilizzare il qualificatore __block in modo che il blocco non acquisisca il valore non ancora assegnato del riferimento del blocco.
  • Se si esegue la gestione manuale della memoria, è necessario autorizzare automaticamente il blocco copiato.
+0

Questo non funzionerebbe in ARC, ma per quanto riguarda la funzione "copia" autorelease] "' "invece di copiare, in modo da non dover chiamare il rilascio all'interno del blocco? Per ARC forse stai facendo '' 'aBlock = nil''' dopo aver chiamato' '' aBlock (10); '' '? –

+0

In realtà, dopo aver rivisitato questa soluzione, risulta che mantenere il riferimento all'interno del blocco causerebbe una perdita se il blocco non veniva richiamato. Ora ho aggiornato la risposta in modo che funzioni correttamente se la chiami o meno. – Karl

+0

Ho un problema che aggiunge un livello di complessità a questo, ma non sono del tutto sicuro che il mio problema sia risolvibile. Mi piacerebbe essere in grado di riprovare le chiamate API tramite AFNetworking. Penso che questo pastebin semplifica e illustra il problema. http://pastebin.com/xRPJuckZ Il blocco ricorsivo unsafe_unretained viene chiamato dall'interno di un altro blocco dentro di sé, e chiamandolo lì porta a un'eccezione di accesso errata. Avresti qualche idea di come risolvere questo? –

3

Non c'è ancora self per blocchi. È possibile costruire uno come questo (supponendo ARC):

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

Il __block è necessario in modo che possiamo impostare blockSelf al blocco dopo aver creato il blocco. Il __weak è necessario perché altrimenti il ​​blocco avrebbe un forte riferimento a se stesso, il che causerebbe un forte ciclo di riferimento e quindi una perdita di memoria. È necessario lo copy per assicurarsi che il blocco venga copiato nell'heap. Questo potrebbe non essere necessario con le versioni più recenti del compilatore, ma non farà alcun danno.

Problemi correlati