Pagine

2014-06-12

NSOutlineView, NSTreeController e la gestione della memoria

Quando vogliamo costruire una NSOutlineView cell-based tramite un NSTreeController, anche se le cose non sono così chiare, sappiamo bene cosa fare! Oltre ad implementare il protocollo del delegate dell'Outline, dobbiamo anche usare la classe di convenienza NSTreeNode, che in pratica rappresentano i nodi; questi oggetti, anche se decidiamo di dimenticarli, vengono comunque creati.
Allora, visto che ci sono, tanto vale usarli; spesso è utile farne una sottoclasse per aggiungere variabili a noi interessanti (p.es. per legarli via binding alla cella).

La parte importante è il representedObject, che utilizziamo per riporvi l'oggetto che verrà rappresentato dal nodo. Giusto per non lasciare le cose troppo astratte, ogni nodo viene creato con il suo representedObject e poi aggiunto al NSTreeController. Per la radice dell'OutlineView (indexPath=0); il representedObject è un NSDictionary con le grandezze che ci servono:

NSDictionary *theRootObject = @{kOutlineTitle: kRootTitlekOutlineCode: @(kCodeHeader)};
ISNode *radice = [ISNode treeNodeWithRepresentedObject:theRootObject];
[self.listController insertObject:radice atArrangedObjectIndexPath:[NSIndexPath indexPathWithIndex:0]];

dove ISNode è la nostra sottoclasse di NSTreeNode e listController è il NSTreeController. Mentre per un qualunque altro nodo, l'unica differenza è l'IndexPath, che ovviamente avrà un indice in più (cioè saranno tutti figli del nodo radice).
Fin qui tutto a posto. Ma supponiamo di cancellare un oggetto mostrato nell'OutlineView... La prima cosa che ci viene in mente è di trovare l'indexPath dell'oggetto e poi usare i metodi del TreeController:

[self.listController removeObjectAtArrangedObjectIndexPath:path];

e in effetti abbiamo ragione! Il problema è che se proviamo ad aggiungere e togliere oggetti, dopo poco ci troveremo un numero interessante di NSTreeNode vaganti per la RAM, che non se ne vogliono andare! Possiamo vederlo facilmente tramite Instruments!

Non mi sono dato la pena di andare a fondo e scoprire il perché: infatti, una volta che un oggetto viene eliminato da un Controller dovrebbe essere libero e quindi deallocato da ARC alla prima occasione. La mia opinione, non dimostrata, è che in qualche modo sia colpa del representedObject (anche se in realtà dovrebbe essere il TreeNode che trattiene il representedObject e non viceversa). Se qualcuno ha un'idea al proposito... si faccia avanti!

In ogni caso, la soluzione è stata semplice: prima di togliere l'oggetto dal TreeController, mi sono registrato il TreeNode che stava per essere eliminato e, subito dopo il metodo -removeObjectAtArrangedObjectIndexPath, non ho fatto altro che imporlo a nil.

ISNode *selezione = [self.listController selectedObjects][0];
[self.listController removeObjectAtArrangedObjectIndexPath:path];
selezione = nil;

Così facendo, Instrument mostra che tutti i TreeNode eliminati scompaiono dalla RAM.