Cercando tra le pieghe di Internet scorciatoie per la soluzione di problemi di implementazione si conferma la massima “chi fa da e fa per tre” e l’imperativo informatico RTFM.
Su forum dedicati si sente dire che per far funzionare alcuni oggetti Cocoa occorre ricorrere a riti esoterici, conoscenze extraterrestri o artefici magici.
La cosa ci è sembrata strana.
Con la nostra proverbiale curiosità mista a diffidenza abbiamo deciso di indagare questi XFiles con XCode.
Cominceremo questo viaggio nell’ignoto alla scoperta dei segreti nel trittico CoreData+NSTreeController+NSOutlineView.
Il sito ADC (Apple Developer Connection) fornisce un mirabile esempio di utilizzo di questo trittico espresso nei suoi termini elementari, con tanto di tutorial passo-passo annesso. Ebbene quale sarebbe dunque questo segreto che la vulgata tramanda come tale nei forum? (http://www.cocoadev.com/index.pl?NSTreeController)
La questione riguarderebbe in particolare l’adozione (attraverso IB) di un “fetch predicate” parent == nil. Molti lamentano l’aver dovuto “trovare” in modo del tutto autonomo questa quale soluzione per il funzionamento del loro software operante con il suddetto trittico, e questo anche dopo lunghi ed estenuanti tentativi.
Si potrebbe replicare che era sufficiente seguire il tutorial di cui abbiamo accennato.
Ma la vulgata lamenta una carenza di documentazione Apple in merito a questo che è descritto quasi come un “trucco”, non spiegato, ma semplicemente indicato sporadicamente, come appunto in esempi quali il tutorial.
Potremmo anche essere tentati di rispondere con l’abusato “RTFM”, ma vogliamo rispondere articolando un discorso che dimostri come in realtà nulla di quanto si va reclamando doveva essere esplicitamente scritto in un manuale.
Noi siamo degli entusiasti, forse non proprio così “esperti” (vedi articolo precedente); ci piace studiare e conoscere, e di manuali non ne leggiamo mai uno solo. Ci disturba pertanto quando qualcuno punta il dito su responsabilità altrui (in questo caso Apple, ma poteva essere chiunque altro) per quelle che sono evidentemente proprie carenze.
Noi quando non riusciamo in un qualcosa nel campo dell’Informatica non diamo colpa nel ad altri o al destino, ma ci ributtiamo sui libri con più determinazione di prima.
Ma torniamo all’argomento.
Ci si stupisce in certi forum dell’uso di questo fetch predicate: parent == nil.
Le discussioni non dovrebbe essere costruita sul significato di questo predicato; ma sul perché ci si stupisce nello scoprire la sua utilità.
Descriviamo il contesto.
CoreData è un framework recentemente introdotto da Apple (dal 10.4 – Tiger) allo scopo di fornire un meccanismo di persistenza per gli oggetti dei programmi, simile ma non identico a quello che potrebbe offrire un database relazionale. Dalla teoria ER e dai RDBMS trae molta filosofia e nomenclatura, ma non ha lo scopo di replicarne l’implementazione. Comunque è strumento ideale per rappresentare il modello nel paradigma MVC (http://developer.apple.com/macosx/coredata.html).
NSOutlineView è una classe di oggetti di visualizzazione (paradigma MVC) capace di mostrare una “lista di elementi ordinati gerarchicamente”: NSOutlineView è erede di NSTableView e dunque è corretto parlare di lista e non di albero per quanto visualizzato, anche e non solo perché questo è realizzato in istanze di NSTableColumn. In realtà la gerarchia e dunque l’albero (in senso astratto) è ottenuta per tramite di uno speciale controllo (NSTreeController) in grado di fornire i dati sulla gerarchia che consente al NSOutlineView di espandere e navigare l’albero delle informazioni, anche e non solo con accorgimenti grafici queli simbolo di espansione (triangolo) e indentazioni.
Diamo più evidenza a quanto stiamo investigando introducendo un esempio concreto su cui discutere: consideriamo il modello più semplice per la rappresentazione di un albero:
con un attributo e due relazioni padre e figlio.
Se popolassimo questo modello con informazioni in relazione gerarchica ciò che otterremo sarebbe una “tabella” (in termini RDBMS) contenente, per ragioni implementative, non solo l’attributo secondo modello, ma anche alcune informazioni (come attributi privati) per governare la relazione, espresse anche queste in colonne della tabella: immaginiamo dunque la presenza di due colonne parent e children quali espressione delle “informazioni di relazione”.
Se CoreData fosse un database relazionale, caricarne il modello in una istanza di NSTreeController (o altro controller) equivarrebbe ad eseguire (in termini SQL) la seguente richiesta:
SELECT * FROM Entity;
Ovviamente questa sarebbe versione “piatta” delle informazioni, una lista, appunto. Un NSTreeController deve derivare da questa versione “piatta” ogni informazione necessaria a governare la gerarchia, ed in primis a determinare la “radice” dell’albero.
Cosa faremmo in termini SQL per ottenere dunque la “radice” dell’albero espresso nella tabella per come l’abbiamo descritta? In altri termini, come otteniamo quelle righe che non abbiano un padre?
La risposta pare ovvia: semplicemente selezionando quelle con colonna parent nulla, ossia:
SELECT * FROM Entity WHERE parent is null;
Ricorda qualcosa ?
A noi ricorda che la clausola WHERE esprime un predicato, e che in particolare il predicato è “parent is null”; questo potrà essere espresso con una istanza di oggetto NSPredicate in perfetto stile Cocoa:
[NSPredicate predicateWithFormat: @”parent == nil”]
Ora appare chiaro cosa ci ricordava ?
E poi ? Che farne di questo predicato ?
NSTreeController è erede di NSObjectController dunque ha un metodo
– (void)setFetchPredicate:(NSPredicate *)predicate
Questo metodo imposta un “fetch predicate” da utilizzare durante il prelievo dei dati dal modello (CoreData) durante la preparazione automatica del contenuto del controller indotto dall’abilitazione della opzione “Automatically Prepares Content” in Interface Builder.
Un “fetch predicate” è lo strumento con cui CoreData “selezione” elementi di una entità; non utilizzando un fetch predicate si avrebbe l’interezza dei dati di quella particolare entità. La compatibilità di IB e delle classi Cocoa con CoreData implica la definizione di fetch predicate da utilizzare nella selezione degli elementi gestiti (managedObject) direttamente nell’Inspector di IB.
Nota: managedObject – arrangetObject: le due facce del modello; managedObject è la rappresentazione del modello mantenuta da un oggetto controller, tipicamente prelevato dalla persistenza di CoreData; arrangedObject è la rappresentazione del modello che il controller fornisce alla vista.
Analogamente NSTreeController fornisce metodi per controllare la diramazione (relazione figlio), e la condizione di elemento foglia nell’albero attraverso l’indicazione di chiavi Cocoa Bindings per accedere ad attributi dell’entità (o dinamici) che indicano tali condizioni; i metodi sono:
setChildrenKeyPath:
setLeafKeyPath:
Questi metodi hanno anche loro una controparte nell’Inspector di IB per configurare a livello di NIB il comportamento del NSTreeController e di conseguenza del NSOutlineView.
Tutte queste informazioni, per inciso, sono ricavabili semplicemente da http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaBindings/Concepts/CntrlContent.html.
Ci chiediamo allora: a che serve documentare l’ovvio ? Non ci pare questo un problema di assenza di documentazione, ma, ci permettiamo, di assenza di preparazione informatica.
(Fine prima parte)