2009-07-02 14 views
20

Sto lavorando a un'applicazione per iPhone. Ho un oggetto della classe che deve rilasciare numerosi oggetti della classe Block. Ogni Block dispone attualmente di una proprietà che conserva una variabile di istanza della classe .Ho un riferimento circolare. Come posso creare un riferimento debole in Objective-C?

@interface Block : UIImageView { 
    Row *yCoord; 
} 
@property (nonatomic,retain) Row *yCoord; 
@end 

Ogni contiene un NSMutableArray di questi blocchi.

@interface Row : NSObject { 
    NSMutableArray *blocks; 
} 
-(void)addBlock:(Block*)aBlock; 
@end 

@implementation Row 
-(void)addBlock:(Block*)aBlock { 
    [blocks addObject:aBlock]; 
    aBlock.yCoord = self; 
} 
@end 

Comprendo che si tratta di un riferimento circolare. La documentazione di Apple afferma che per deallocare un oggetto con un riferimento circolare ho bisogno di un riferimento debole invece di un riferimento forte (una proprietà retain), ma non segue e spiega esattamente come vado a farlo. Ho intenzione di rilasciare e deallocare tutti i Blocchi all'interno di una Fila e la stessa Riga contemporaneamente. Come posso impostare un riferimento debole all'interno di ciascuno dei miei blocchi nella riga "padre"?

+4

A proposito, devi essere elogiato per la modifica e il tag appropriato della tua domanda, soprattutto come nuovo utente SO. Bravo! Apprezzo le persone che si prendono il tempo per migliorare un po 'le cose per il bene della comunità. –

+2

Perché, grazie! Questo sito è esattamente quello che stavo cercando in ogni aspetto. Ha senso prendersi il tempo di fare una domanda chiara quando così tante persone sono disposte a saltare fuori e rispondere subito. – Tozar

risposta

19

Edit: Dal momento che il richiedente ha chiarito che non sta usando garbage collection (iPhone attualmente non lo supporta), il mio consiglio è di evitare cicli avendo solo uno degli oggetti conserva l'altro, proprio come si farebbe con un delegare. Quando usi proprietà, usa "assegna" invece di "conserva" per ottenere ciò. Per esempio:

@property (nonatomic,assign) Row *yCoord; 

Il resto della mia risposta risposta si riferisce a "riferimenti deboli" in termini di Objective-C 2.0 e GC.


Quando si lavora con la raccolta dei rifiuti (10.5 +), un riferimento debole è stato creato da premettendo una dichiarazione di variabile con __weak. Quando assegni a quella variabile, il GC (se abilitato) tiene traccia del riferimento e lo azzererà automaticamente se tutti i riferimenti forti all'oggetto di riferimento scompaiono. (Se GC non è abilitato, l'attributo __weak viene ignorato.)

Così, puoi tranquillamente modificare la risposta sopra per giocare meglio con la garbage collection (attualmente su 10.5+, e forse un giorno su iPhone) come segue: (Vedi la related Apple docs)

@property (nonatomic,assign) __weak Row *yCoord; 

Per citare Chris Hanson (dove si possono trovare informazioni più dettagliate):.

"anteponendo una dichiarazione variabile di istanza con __weak, vi dico la garbage collector che se è l'unico r eferenza per un oggetto che l'oggetto dovrebbe essere considerato collezionabile. "

Vorrei chiarire che dicendo "se non ci sono riferimenti non deboli a un oggetto". Non appena viene rimosso l'ultimo riferimento forte, l'oggetto può essere raccolto e tutti i riferimenti deboli verranno azzerati automaticamente.

Nota: Questo non direttamente correlati alla creazione riferimenti deboli, ma c'è anche un attributo __strong, ma poiché variabili oggetto Objective-C sono forti riferimenti per impostazione predefinita, è generalmente utilizzato solo per puntatori C grezzi a cose come le strutture o le primitive che il Garbage Collector non tratterà come radici e saranno raccolti da sotto di te se non li dichiari forti. (Considerando la mancanza di __weak può causare mantenere cicli e perdite di memoria, la mancanza di __strong può comportare rientravano memoria e davvero strani e insidiosi insetti che si verificano non deterministica e può essere molto difficile da rintracciare.)

+1

Permettetemi di chiarire che questo è per un'applicazione iPhone. Non credo che l'iPhone usi un GC. – Tozar

+1

quindi anziché dichiarare Riga * yCoord; con @property (non anatomico, retain) \t Row * yCoord; Devo fare: __weak Row * yCoord; Ma come posso dichiarare la proprietà? – Tozar

+0

È possibile mixare @property e __weak: @property (nonatomic, retain) __weak Row * yCoord; – Tim

4

Una debole il riferimento è semplicemente un compito (a meno che tu non stia parlando di Garbage Collection, che è un'intera lattina separata di worm, ma non soffre di cicli di conservazione).

Normalmente, in Cocoa, manterrebbe il Block oggetti (inserendole nel NSMutableArray), ma Block non manterrebbe , ciascuno avrebbe semplicemente memorizzarlo in un Ivar (con una struttura a "assegnare").

Finché è attento a rilasciare ogni Block prima di essere deallocato (vale a dire, la sua dealloc dovrebbe rilasciare il NSMutableArray che rilascerà i blocchi finché nessun altro ha tutti i puntatori a loro), allora tutto sarà deallocata come adeguata.

Si può anche prendere la precauzione di azzeramento del riferimento di riga da blocchi prima di rimuovere le entiries dalla matrice, qualcosa come:

- (void) dealloc { 
    for (Block* b in _blocks) { 
     b.row = nil; 
    } 
    [_blocks release]; 
    [super dealloc]; 
} 

dove _blocks è l'Ivar fa riferimento la proprietà blocchi.

+0

È _b il NSMutableArray ivar restituito da self.blocks? Se è così, suppongo che intendi [_b release] invece. Inoltre, non è necessario utilizzare self.blocks all'interno della classe che contiene i referencs - basta usare per (Block * b in _b). Potrebbe essere meno confuso se _b fosse chiamato "_blocks" o anche "blocks". (Mi rendo conto che alcune persone preferiscono il trattino basso, ma non lo uso mai.) –

+0

Risolto, grazie Quinn –

8

Basta cambiarlo per assegnarlo invece di conservare, non più riferimenti circolari.

@interface Block : UIImageView { 
    Row *yCoord; 
} 
@property (nonatomic,assign) Row *yCoord; 
@end 
3

Utilizzo assegnare per creare riferimenti deboli può essere pericoloso in un sistema multithread, in particolare quando sia oggetto può essere trattenuto da un terzo oggetto, e poi utilizzato per dereference l'altro oggetto.

Fortunatamente, questo è spesso un problema di gerarchia e l'oggetto che contiene il riferimento debole si preoccupa solo dell'oggetto a cui fa riferimento per la durata dell'oggetto indicato. Questa è la solita situazione con una Superior < -> Relazione subordinata.

Penso che il caso nel commento dell'OP sia mappato a questo, con Riga = Superiore, Blocco = Subordinato.

In questo caso, vorrei utilizzare una maniglia per riferirsi al Superiore dal Subordinato:

// Superior.h 

@class Superior; 

@interface SuperiorHandle : NSObject { 
    @private 
     Superior* superior_; 
} 

// note the deliberate avoidance of "nonatomic" 
@property (readonly) Superior *superior; 

@end 

@interface Superior : NSObject { 
    @private 
     SuperiorHandle *handle_; 
     // add one or more references to Subordinate instances 
} 

// note the deliberate avoidance of "nonatomic" 
@property (readonly) SuperiorHandle *handle; 

@end 


// Superior.m 

#import "Superior.h" 

@implementation SuperiorHandle 

@synthesize 
    superior = superior_; 

- (id)initWithSuperior:(Superior *)superior { 
    if ((self = [super init])) { 
     superior_ = superior; // weak reference 
    } 
} 

- (void)invalidate { 
    @synchronized (self) { 
     superior_ = nil; 
    } 
} 

- (Superior *)superior { 
    @synchronized (self) { 
     // retain and autorelease is required to prevent dealloc before we're ready, thanks to AndroidDev for pointing out this mistake 
     return [[superior_ retain] autorelease]; 
    } 
} 

@end 

@implementation Superior 

@synthesize 
    handle = handle_; 

- (id)init { 
    if ((self = [super init])) { 
     handle_ = [[SuperiorHandle alloc] initWithSuperior:self]; 
    } 
    return self; 
} 

- (void)dealloc { 
    [handle_ invalidate]; 
    [handle_ release]; 

    [super dealloc]; 
} 

@end 


// Subordinate.h 

@class Superior; 
@class SuperiorHandle; 

@interface Subordinate : NSObject { 
    @private 
     SuperiorHandle *superior_handle_; 
} 

@property (readonly) Superior *superior; 

@end 


// Subordinate.m 

#import "Subordinate.h" 

#import "Superior.h" 

@implementation Subordinate 

// no synthesize this time, superior's implementation is special 

- (id)initWithSuperior:(Superior *)superior { 
    if ((self = [super init])) { 
     superior_handle_ = [superior.handle retain]; 
    } 
    return self; 
} 

- (void)dealloc { 
    [superior_handle_ release]; 

    [super dealloc]; 
} 

- (Superior *)superior { 
    @synchronized (superior_handle_) { 
     return superior_handle_.superior; 
    } 
} 

@end 

Alcuni vantaggi:

  1. filo di sicuro. Non è possibile che il riferimento debole contenuto in Subordinato diventi un puntatore non valido. Potrebbe diventare nulla, ma va bene.
  2. Solo gli oggetti devono conoscere il riferimento debole incorporato. Tutti gli altri oggetti possono considerare Subordinato come se avesse un riferimento regolare a Superior.
+0

Questo disegno funzionerà, ma non è possibile usare @synthesize superior nella classe SuperiorHandle. @synthesize creerà un blocco separato per ogni campo (uno spin lock per essere esatti). È necessario bloccare lo stesso blocco che si utilizza in 'invalidate' che si esegue quando si restituisce la variabile 'superior_'. Quindi è necessario implementare se stessi superiori e restituire @synchronized (self) {returnValue = _superior} return returnValue; –

+0

In realtà, forse questo design "Superior" non funzionerà. Cosa succede se si chiama il metodo 'superiore' per ottenere il valore per 'Superiore', e quindi un altro thread invalida l'handle? Ti rimane ancora un puntatore alla memoria liberata nella discussione in cui hai appena ottenuto il valore 'superiore'. A meno che, ovviamente, non stai assumendo che nulla di tutto ciò sia sicuro per il thread (ma se così fosse, non lo faresti t bisogno di @ sincronizzare le chiamate). Sfortunatamente non penso che funzionerà affatto. Hai davvero bisogno di un ambiente GC perché funzioni, oppure devi mantenere/autorizzare il valore superiore prima di restituirlo. –

+0

Re: spin lock. Non ero a conoscenza di questo comportamento, hai una documentazione per questo? Quello che ho letto nel libro di Hillegass e ciò che è documentato [qui] (http://stackoverflow.com/questions/588866/atomic-vs-nonatomic-properties) indica che il blocco generato da "synthesize atomic" è @ sincronizzato (auto) proprio come faccio manualmente in invalidate(). – Brane

Problemi correlati