15

Quindi sto usando i blocchi ricorsivi. Capisco che affinché un blocco sia ricorsivo, deve essere preceduto dalla parola chiave __block e deve essere copiato in modo che possa essere messo sullo heap. Tuttavia, quando lo faccio, si presenta come una perdita in Strumenti. Qualcuno sa perché o come posso aggirarlo?Blocchi ricorsivi in ​​Objective-C che perdono in ARC

Si prega di notare nel codice qui sotto ho riferimenti a molti altri blocchi, ma nessuno di essi è ricorsivo.

__block NSDecimalNumber *(^ProcessElementStack)(LinkedList *, NSString *) = [^NSDecimalNumber *(LinkedList *cformula, NSString *function){ 
     LinkedList *list = [[LinkedList alloc] init]; 
     NSDictionary *dict; 
     FormulaType type; 
     while (cformula.count > 0) { 
      dict = cformula.pop; 
      type = [[dict objectForKey:@"type"] intValue]; 
      if (type == formulaOperandOpenParen || type == formulaListOperand || type == formulaOpenParen) [list add:ProcessElementStack(cformula, [dict objectForKey:@"name"])]; 
      else if (type == formulaField || type == formulaConstant) [list add:NumberForDict(dict)]; 
      else if (type == formulaOperand) [list add:[dict objectForKey:@"name"]]; 
      else if (type == formulaCloseParen) { 
       if (function){ 
        if ([function isEqualToString:@"AVG("]) return Average(list); 
        if ([function isEqualToString:@"MIN("]) return Minimum(list); 
        if ([function isEqualToString:@"MAX("]) return Maximum(list); 
        if ([function isEqualToString:@"SQRT("]) return SquareRoot(list); 
        if ([function isEqualToString:@"ABS("]) return EvaluateStack(list).absoluteValue; 
        return EvaluateStack(list); 
       } else break; 
      } 
     } 
     return EvaluateStack(list); 
    } copy]; 
    NSDecimalNumber *number = ProcessElementStack([formula copy], nil); 

UPDATE Così nella mia ricerca ho scoperto che il problema a quanto pare non hanno a che fare con i riferimenti alle altre blocchi questo blocco utilizza. Se faccio qualcosa di semplice come questo, non perde:

__block void (^LeakingBlock)(int) = [^(int i){ 
     i++; 
     if (i < 100) LeakingBlock(i); 
    } copy]; 
    LeakingBlock(1); 

Tuttavia, se posso aggiungere un altro blocco in questo, lo fa perdita:

void (^Log)(int) = ^(int i){ 
    NSLog(@"log sub %i", i); 
}; 

__block void (^LeakingBlock)(int) = [^(int i){ 
    Log(i); 
    i++; 
    if (i < 100) LeakingBlock(i); 
} copy]; 
LeakingBlock(1); 

Ho provato con il __block parola chiave per Log() e anche provato a copiarlo, ma perde ancora. Qualche idea?

UPDATE 2 Ho trovato un modo per prevenire la perdita, ma è un po 'oneroso. Se converto il blocco passato in un ID debole e quindi restituisco l'ID debole in un tipo di blocco, posso impedire la perdita.

void (^Log)(int) = ^(int i){ 
    NSLog(@"log sub %i", i); 
}; 

__weak id WeakLogID = Log; 

__block void (^LeakingBlock)(int) = [^(int i){ 
    void (^WeakLog)(int) = WeakLogID; 
    WeakLog(i); 
    if (i < 100) LeakingBlock(++i); 
} copy]; 
LeakingBlock(1); 

Sicuramente c'è un modo migliore?

+0

Grazie per aver condiviso la tua ricerca, non ho sentito di dover copiare anche il blocco. Tuttavia, sembra che un LLVM più recente emetta un avviso alla chiamata ricorsiva "catturare LeakingBlock" in questo blocco è probabile che porti a un ciclo di conservazione ".L'unico modo che ho trovato per placare il compilatore è quello di usare un ptr debole separato per il blocco in qualche modo simile alla tua risposta qui sotto, anche se è abbastanza maneggevole che sono tentato di sovrascrivere localmente l'avviso. Sarei interessato a vedere la tua opinione quando proverai l'ultimo compilatore. –

+0

@smallduck Originariamente, ho usato 'copia' perché fa sì che il blocco venga copiato nell'heap dalla pila. Per un po 'ha funzionato bene e ho anche ricevuto l'errore "ricorsivo" del compilatore. Ho rimosso 'copia' dal mio codice (come riportato nella mia risposta) e ha funzionato (mentre prima avrei avuto' EXC_BAD_ACCESS'. Sto supponendo che Apple abbia modificato la parola chiave '__block' per creare blocchi sull'heap piuttosto che su lo stack ... ma è solo una supposizione: –

+0

@smallduck Sinceramente, ho rinunciato a usare i blocchi per la ricorsione Sì, può essere fatto ma è un po 'pesante e ci sono troppe insidie. È troppo facile finire con mantiene i cicli (che possono essere davvero pessimi con la ricorsione) e diventa difficile da leggere.Quindi di solito mi limito a metodi/funzioni per fare la ricorsione .. –

risposta

11

Ok, ho trovato la risposta da solo ... ma grazie a chi ha cercato di aiutare.

Se si sta facendo riferimento/utilizzando altri blocchi in un blocco ricorsivo, è necessario passarli come variabili deboli. Ovviamente, __weak si applica solo ai tipi di puntatori a blocchi, quindi è necessario digitarli per primi. Ecco la soluzione finale:

typedef void (^IntBlock)(int); 

    IntBlock __weak Log = ^(int i){ 
     NSLog(@"log sub %i", i); 
    }; 

    __block void (^LeakingBlock)(int) = ^(int i){ 
     Log(i); 
     if (i < 100) LeakingBlock(++i); 
    }; 
    LeakingBlock(1); 

Il codice di cui sopra non perde.

+1

Non è necessario il typedef se non lo si desidera: 'void (^ __ weak log) (int) =^(int i) {...};' –

+2

@MattWilding Buona cattura. Penso che non funzionasse nelle versioni precedenti di Xcode. Il compilatore sembra essere in costante cambiamento per quanto riguarda i blocchi. Ho appena scaricato Xcode 4.6 e ora si lamenta che il codice sopra "può" portare a un ciclo di conservazione. Penso che Apple stia ancora cercando di capire l'intera cosa del "Block". –

+1

Mi sembra di cambiare il mio codice di blocco con ogni revisione del compilatore. Avevo lo stesso avvertimento con 4.6, e puoi correggerlo contrassegnando 'LeakingBlock' con il modifer' __weak'. –

-1

Senza ulteriori informazioni di contesto, posso dire questo:

Si sono perdite che blocco perché si sta copiando e non rilasciarlo altrove. Devi copiarlo per spostarlo nell'heap, va bene. Ma il modo in cui hai scelto non è del tutto ok.

Un modo corretto per eseguirlo è archiviarlo come variabile di istanza di un oggetto, copiarlo e quindi rilasciarlo all'interno di dealloc. Almeno, è un modo per farlo senza perdite.

+2

Non riesco a rilasciarlo manualmente. Sto usando ARC (conteggio automatico dei riferimenti) che impedisce L'ambito del blocco è il metodo, quindi l'utilizzo di un iVar è l'odore del codice.Il blocco dovrebbe essere rilasciato alla fine del metodo –

+0

Sta usando ARC – Eonil

0

Aaron,

il tuo codice sembra essere a thread singolo, perché stai copiando il blocco? Se non si copia il blocco, non si ha una perdita.

Andrew

+0

Sì, sei corretto, il codice è singolo infilato. Non sono sicuro di me capisco il punto di quella dichiarazione però. Se non copio il blocco, ottengo EXC_BAC_ACCESS quando il blocco tenta di accedere a se stesso in modo ricorsivo. –

+1

In realtà, dovrei chiarire: senza copiare il blocco, ottengo EXC_BAD_ACCESS sull'assegnazione, non quando il blocco tenta di accedere a se stesso in modo ricorsivo (che si verifica se non utilizzo __block). Sono un po 'insicuro dei dettagli, ma credo che sia perché il blocco viene inizialmente creato come oggetto const nello stack, mentre il blocco a cui si fa riferimento all'interno è lo stesso blocco const stack. Ciò viene risolto copiando prima il blocco sull'heap, quindi assegnandolo al __block var in modo che possa fare riferimento a una copia di se stesso piuttosto che a se stessa letterale. –