Archivi tag: Cocoa

cocoa_cup

OSX e le risorse dinamiche

Molte applicazioni di piccole dimensioni nascono e muoiono con il solo contributo delle risorse definite nel nib/xib MainMenu contenuto nel bundle dell’applicazione.
Ma la disponibilità di risorse nella programmazione macOS è molto più articolata e al crescere della dimensione e funzionalità di una applicazione questa disponibilità verrà prima o poi a convergere nella nostra applicazione.

Vediamo di dare una guida ragionata all’uso di questa disponibilità di risorse.

Le diverse forme di disponibilità vanno scelte in ragione della tipologia di risorsa che intendiamo dinamizzare ed in ragione di quante istanze di questa abbiamo bisogno.

Una ulteriore scelta potrebbe essere fatta in ragione del budle che contiene le risorse che ci interessa utilizzare: potremmo infatti considerare l’uso di risorse collocate diversamente dal bundle dell’applicazione; ma per ora tralasciamo questa possibilità.

Iniziano con il distinguere in base alla dimensione della astrazione che intendiamo recuperare dinamicamente: una intera finestra o una vista custom.

All’interno di questa distinzione possiamo trovare una ulteriore dicotomia in relazione al numero di controller collegati alle viste: un solo controller o più controller.

Pur potendo il nib/xib contenere istanze di oggetti controller, quello che a noi interessa è l’istanza (o istanze) di controller che richiedano il caricamento dinamico di risorsa, e non quindi controller che subiscano essi stessi il meccanismo di caricamento dinamico stesso.

Dal punto di vista del nib/xib esiste un riferimento speciale ad un oggetto che è inteso sempre disponibile per le viste ivi contenute perché responsabile del caricamento dell’intero nib/xib: questo riferimento è il File’s Owner. Si può considerarlo spesso un oggetto di tipo controller o comunque un proxy di qualche genere a modello o azioni (a cui collegare le viste).

In virtù di questo il framework AppKit provvede a fornirci due implementazioni di controller con cui governare (prima delle altre cose accessorie in grado di fare) il caricamento dinamico di una intera finestra o di una specifica vista; sono: NSWindowController e NSViewController. L’implementazione di base è spesso sufficiente allo scopo del caricamento dinamico, comunque non è infrequente che si possa necessitare di una loro estensione per compiti poi specifici.
Perché le viste all’interno del nib/xib abbiano accesso ad un controller o model validi è sufficiente fare affidamento al File’s Owner, definendolo nel nib/xib come di classe scelta per il controllo del caricamento della risorsa rappresentata nel nib/xib (quindi NSWindowController o NSViewController, oppure loro derivazioni).
A questo punto una istanza di NSWindowController potrà tramite -(id)initWithWindowNibName:(NSString *)windowNibName
consentire il recupero di una intera finestra da un nib/xib (indicato per nome) con tutte le azioni, outlet o binding collegate a File’s Owner (e quindi NSWindowController) o suoi elementi.
La finestra stessa è ottenibile dall’istanza di NSWindowController mediante metodo -window, così come sono ottenibili altri strumenti propri del controller.

Esempio

NSWindowController * aWindowController = [[NSWindowController alloc] initWithWindowNibName:@"myWindow"];

[aWindowController showWindow: self];

Allo stesso modo un NSViewController potrà farsi carico del caricamento di un nib/xib che definisce una vista attraverso il metodo:

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

L’idea di questo controller (contrariamente al precedente, orientata alla finestra/documento) è maggiormente sviluppata al supporto per le viste contenute nel nib/xib nella definizione di legami con il modello; a tale scopo fornisce una proprietà generica representedObject per consentire un facile collegamento tra il nib e un oggetto (modello) che ancora non è noto in fase di caricamento del nib/xib.

Esempio

NSViewController *viewCtlr = [[[NSViewController alloc] initWithNibName:@"myView" bundle:nil] autorelease];

[[window contentView] addSubview: [viewCtlr view]];

Questi due controller risolvono il problema di una vista più o meno complessa governata da un proprio controller. Ma come poter gestire istanze multiple di viste custom afferenti al medesimo controller?

Una derivazione da NSView può implementare la seguente:

- (BOOL)loadFromNib:(NSString *)nibName bundle:(NSBundle *)bundle controller:(id)controller
{
/*
 * Codice liberamente tratto da "Resource Programming Guide" di Apple
 *
 */

    NSNib *		aNib = [[NSNib alloc] initWithNibNamed:@"ReusableView" bundle:bundle];

    NSArray *	topLevelObjs;

    if (![aNib instantiateNibWithOwner:controller topLevelObjects:&topLevelObjs]) {

     NSLog(@"Warning! Could not load nib file.\n");

     return NO;

    }

    // Ricerca delle viste

   for(id obj in topLevelObjs) {

     if ( [obj isKindOfClass: [NSView class]] )

        [self addSubview: obj];

}

   // Release the raw nib data.

    [aNib release];

    // Release the top-level objects so that they are just owned by the array.

    [topLevelObjs makeObjectsPerformSelector:@selector(release)];

   // Do not autorelease topLevelObjs.

    return YES;

}

Esempio

ReusableView * reusable1  = [[ReusableView alloc] initWithFrame:NSMakeRect(0, 0, 100, 95)];

[reusable1 loadFromNib:@"ReusableView" bundle:nil controller: controller];

[reusable2 loadFromNib:@"ReusableView" bundle:nil controller: controller];

Un limite di questa implementazione è l’assenza di un meccanismo automatico per definire la catena dei risponditori sommando il contributo di ciascuna vista (e poter passare da una a l’altra, che detengono così catene indipendenti).
Per semplificare rapporto con model implementare una proprietà representedObject similmente a NSViewController.

Va da se che applicare questa forma di caricamento quando si collegano poi alle viste istanze di controller differenti è una inutile ripetizione della implementazione già in essere con NSViewController.
Con questa ultima osservazione chiudiamo la nostra guida al caricamento dinamico delle risorse in macOS.

Alla prossima.

IMG_3049.PNG

Estendere le capacitˆà di stampa di Cocoa

Avete realizzato l’interfaccia per cdlabelgen come descritto nel precedente post?

Beh, io si e ho affrontato anche il problema della stampa: voi?

La stampa in programmi Cocoa deriva dall’essenziale derivazione del motore grafico Quartz dal PDF. Quindi in Cocoa la stampa nasce dalla costruzione di un suo oggetto NSView da cui discendano come contenuto tutte le componenti grafiche necessarie a strutturare il layout finale.

Quello che otteniamo è dunque una relazione 1:1 tra widget Cocoa, testo e immagini e le componenti PDF che porteremo alla stampa.

Ma con cdlabelgen abbiamo a che fare con postscript: questo è ciò che viene generato dallo script. Che PDF e postscript siano parenti non aiuta affatto!

Cercando in Internet non ho trovato soluzioni, nemmeno lontanamente. Che la cosa non fosse mai stata affrontata o fosse stata risolta con librerie proprietarie dai software di grafica con tanto di blasone mi sembrava alquanto strana.

Poi (la disperazione a volte aiuta!) mi sono ricordato che nella cartella esempi di XCode qualcosa riguardo la stampa era presente: ho cominciato a cercare e studiare, tornando sull’indispensabile (e preziosa) documentazione ADC.

Ed ecco a voi la soluzione (Apple, non mia: io ho solo ritrovato e ora pubblicizzato tale soluzione).

Dicevamo che i frameworks che compongono l’universo Cocoa (in particolare AppKit) supportano una limitata visione del processo di stampa, segnatamente una versione NSView-centrica gestita attraverso le seguenti astrazioni:

  • NSPageLayout
  • NSPrintInfo
  • NSPrintOperation
  • NSPrintPanel
  • NSPrinter

Un supporto completo alla stampa deriva (da indicazione esplicita in documentazione) dall’utilizzo diretto del framework Core Printing, una API in C di cui AppKit crea una interfaccia limitata.

La soluzione da noi cercata per stampare dati postscript (file, in particolare) è la funzione PMPrinterPrintWithFile presente in questo framework; il suo prototipo è:

OSStatus PMPrinterPrintWithFile (

 PMPrinter printer,

 PMPrintSettings settings,

 PMPageFormat format,

 CFStringRef mimeType,

 CFURLRef fileURL

);

Come possiamo vedere mediante il parametro fileURL si fornisce un percorso al file da stampare, file di cui mediante parametro mimeType si indica anche il tipo. La specifica di tipo è importante in quanto deve coincidere con uno dei tipi supportati dal framework; i tipi supportati (ricavati dalla funzione per la validazione del tipo passato) sono:

  •     “application/octet-stream”
  •     “application/pc-eps”
  •     “application/pdf”
  •     “application/pictwps”
  •     “application/postscript”
  •     “application/vnd.apple-postscript”
  •     “application/vnd.cups-banner”
  •     “application/vnd.cups-command”
  •     “application/vnd.cups-postscript”
  •     “application/vnd.cups-raster”
  •     “application/vnd.cups-raw”
  •     “application/vnd.hp-hpgl”
  •     “application/x-cshell”
  •     “application/x-csource”
  •     “application/x-perl”
  •     “application/x-shell”
  •     “application/xhtml+xml”
  •     “image/fuji-raw”
  •     “image/gif”
  •     “image/imageio”
  •     “image/jp2”
  •     “image/jpeg”
  •     “image/minolta-raw”
  •     “image/openexr”
  •     “image/pict”
  •     “image/png”
  •     “image/rad”
  •     “image/tiff”
  •     “image/x-alias”
  •     “image/x-bitmap”
  •     “image/x-bmp”
  •     “image/x-ico”
  •     “image/x-icon”
  •     “image/x-photocd”
  •     “image/x-portable-anymap”
  •     “image/x-portable-bitmap”
  •     “image/x-portable-graymap”
  •     “image/x-portable-pixmap”
  •     “image/x-psd”
  •     “image/x-qtif”
  •     “image/x-sgi-rgb”
  •     “image/x-sun-raster”
  •     “image/x-tga”
  •     “image/x-xbitmap”
  •     “image/x-xpixmap”
  •     “text/html”
  •     “text/plain”
  •     “text/rtf”

Non male, vero?

È ovvio che per invocare questa funzione occorre completarne la lista argomenti: stampante, formato e settaggio per il processo di stampa.
Questi ultimi due parametri li ricaviamo fortunatamente direttamente in AppKit, in particolare da una istanza condivisa di NSPrintInfo 

 [NSPrintInfo sharedPrintInfo]

dopo averla popolata utilizzando NSPrintPanel e NSPageLayout (le componenti Cocoa che realizzano le finestre di dialogo utente per le rispettive finalitˆà utilizzando una istanza di NSPrintInfo). Per Il parametro stampante invocheremo PMSessionGetCurrentPrinter() passando la sessione ottenuta sempre da NSPrintInfo dopo averle collegato settaggio e formato mediante PMSessionDefaultPrintSettings() e PMSessionValidatePageFormat() (funzioni di Core Printing).

Mettendo assieme il tutto avrete la vostra stampa di file postscript.
Questo un rapido quadro della soluzione: per i dettagli c’e’ la documentazione: replicarla qui mi sembra troppo!

Alla prossima!

IMG_2826.JPG

Cocoa & Perl: come ti faccio diventare uno script una applicazione Cocoa

Torniamo a parlare di programmazione Cocoa.

Questa volta parliamo di come integrare una interfaccia Cocoa ad una preesistente applicazione a linea di comando, e perché no, persino un’applicazione interpretata.

Avete preferenze?

Io una si.

Ho scelto di integrare uno script perl che ho sempre utilizzato in Linux e che volevo anche su Mac, ovviamente in una veste più “consona”, passatemi il termine.

Si tratta di cdlabelgen (http://www.aczoom.com/tools/cdinsert/), un utilissimo strumento per generare rapidamente copertine e buste per CDROM e DVD.

Scaricare e provare lo script è stata la prima cosa, anche per verificare quali potevano essere i requisiti per il suo funzionamento su OSX. Tutto tranquillo: funziona senza ulteriori installazioni.
La veste più consona è ovviamente la veste grafica: scartati Automator e AppleScript quali potenziali strumenti, la soluzione è ovviamente una applicazione Cocoa. Come in molti porting di software open source l’applicazione essenzialmente avrà il compito di generare la lista argomenti conseguente alle scelte utente (frontend) e attivare il programma di backend (nel nostro caso cdlabelgen): per non essere dipendente da fattori esterni l’applicazione conterrà lo script nel proprio bundle.

Volendo essere precisi, trattandosi di uno script interpretato, l’applicazione attivata sarà l’interprete perl del sistema a cui verrà passato il nome dello script con i suoi argomenti.
Il risultato mi è sembrato soddisfacente, quindi ve lo propongo. 
Vediamo come si realizza.
Per prima cosa creiamo un nuovo progetto Cocoa Application nel nostro XCode e copiamo lo script cdlabelgen nel gruppo Resources del nostro progetto; parimenti facciamo anche della cartella “postscript” fornita assieme allo script (ci sono i modelli grafici di riferimento).
Poi in un metodo di delega (applicationDidFinishLaunching) dell’applicazione Cocoa leggiamo il percorso alle risorse del bundle: ci serviranno per avviare lo script, in particolare per fornire il percorso delle risorse come program working directory a cdlabelgen in cui cercherà la cartella postScript.

NSString * programWorkingDirectory = [[[NSBundle mainBundle] resourcePath] retain];

NSString * commandPath = [[programWorkingDirectory stringByAppendingPathComponent: @"cdlabelgen"] retain];

(Ovviamente creiamole come variabili dell’istanza di NSObject <NSApplicationDelegate> di cui scegliamo un metodo in cui inserire il precedente codice).
Creiamo quindi una interfaccia per raccogliere le opzioni per la linea di comando ed un pulsante “Vai”, “Disegna”, “Go”, “Salva” o qualsiasi cosa gradiate. Colleghiamo il selettore di questo pulsante al metodo – (IBAction)save:(id)sender che avrete creato nel delegato dell’applicazione.

In questo metodo eseguiremo quanto necessario all’esecuzione di cdlabelgen e alla raccolta eventuale dei suoi frutti.

Un piccolo accenno su come costruire rapidamente una interfaccia per gli argomenti.
Create una istanza di oggetto NSUserDefaultController nello XIB; otterrete un istanza “Shared”; collegate i value dei widget al controller key “value” dell’istanza condivisa di User Defaults: non solo avrete un oggetto per rendere “attive” e visibili al programma le scelte effettuate, ma avrete anche una persistenza delle ultime configurazioni scelte, tutto in uno!

Solo per i tipi derivati da NSPopupMenu c’é una piccola eccezione: occorre in questo caso collegare il “Selected Index”: il value contenuto è per noi solo una label.
Per la costruzione della linea di comando, comprensiva delle opzioni, sarà ora sufficiente decodificare tutte le chiavi negli userDefaults elencandole in un array (è quanto vuole l’API di esecuzione di programmi esterni); ad esempio in questo modo:

NSMutableArray * args = [[NSMutableArray alloc] init];

[args addObject: commandPath];

if ( [[[NSUserDefaults standardUserDefaults] valueForKey:@"noTrayPlaque"] boolValue] == YES )

[args addObject:@"--no-tray-plaque"];

Forse non è elegante, ma questo metodo è il più rapido da realizzare senza dover progettare un complesso modello che tenga conto delle varie necessità o scomodare Core Data. Ricorsarsi di applicare prime il metodo synchronize sui defaults prima di iniziare:

[[NSUserDefaults standardUserDefaults] synchronize];

Per la parte operativa utilizziamo una istanza di NSTask per eseguire il comando esterno.
Per configurare l’esecuzione occorre il percorso del programma interprete.
Su Terminale quindi eseguiamo questo comando:

iMac:~ andrea$ which perl

/usr/bin/perl

Portiamo questo a conoscenza di una istanza NSTask:

NSTask * task = [[NSTask alloc] init];
task.launchPath = @"/usr/bin/perl";

task.currentDirectoryPath = programWorkingDirectory;

oppure (variante senza property):

[task setLaunchPath: @"/usr/bin/perl"];

[task setCurrentDirectoryPath: programWorkingDirectory];

così come per gli argomenti:

NSMutableArray * args = [[NSMutableArray alloc] init];

[args addObject: commandPath];
[args addObject: @"-o"];
[args addObject: filename];

Per ovviare i problemi di bufferizzazione di NSPipe, invece di “leggere” lo standard output di cdlabelgen gli passiamo sempre l’opzione “–output-file” con un nome di file precedentemente fornito dall’utente con un NSSavePanel.
Non rimane che lanciare il nostro programma:

task.arguments = args;
[task launch];

avremo il nostro file generato nel percorso e nome dati.
È opportuno dare un riscontro all’utente con una finestra di dialogo.

Per questo è sufficiente porre un risponditore alla notifica NSTaskDidTerminateNotification che possa presentare un semplice NSAlert. La notifica è lanciata da NSTask al termine del processo, e questo completa l’interfaccia utente.
Potrete aggiungere a piacimento tutto quello che ritenete utile ad una applicazione compiuta, ivi compresa una icona accattivante.

Ovviamente , se decidete di realizzare questo progetto e lo distribuite (anche tra amici), ricordatevi di inserire le credenziale degli sviluppatori di cdlabelgen in qualche parte dell’interfaccia, ossia Avinash Chopde <avinash@aczoom.com> www.aczoom.com e B. W. Fitzpatrick <fitz@red-bean.com>

Al prossimo esperimento.

cocoa_cup

Introduzione a Cocoa – Parte III

KVO, KVC & Binding
Key-Value Observing (KVO) è un meccanismo che consente agli oggetti di ricevere notifiche su cambiamenti di specifiche proprietà di altri oggetti.

E’ basato sul protocollo informale NSKeyValueObserving.

Le proprietà osservate possono essere semplici attributi, relazioni uno-a-uno o uno-a-molti. Nel contesto MVC, questo meccanismo è particolarmente importante in quanto consente agli oggetti della vista di osservare i cambiamenti negli oggetti del modello attraverso il livello controllo.
Essenziale in questo la tecnologia Cocoa Bindings.

Cocoa fornisce molti oggetti della capacità di essere osservati attraverso il protocollo NSKeyValueObserving.

Key-Value Coding
Key-Value Coding (KVC) è un meccanismo per accedere in modo indiretto agli attributi e relazioni degli oggetti usando identificatori stringa.
kvc

Tecnica sottointesa da molti meccanismi e tecnologie Cocoa; particolare rilevanza ha invero nella programmazione di applicazioni Core Data, con uso intensivo di binding, e altro.
Il meccanismo KVC è reso possibile da protocollo informale NSKeyValueCoding.
Due metodi di questo protocollo (valueForKey: e setValue:forKey:) sono particolarmente importanti in quanto consentono di leggere e scrivere valori delle proprietà di oggetti conformi mediante una chiave di accesso nominale (una stringa con il nome della proprietà). NSOject fornisce una implementazione base di questi metodi. Specializzazioni sono possibili.
Implementare questi metodi può significare dotare le classi di proprietà “virtuali”, ossia non corrispondenti a effettivi attributi negli oggetti, ma “calcolati” o comunque gestiti in modo meno diretto (composti da più proprietà, ecc).
Stabilire collegamenti tra oggetti via KVO è una capacità di Interface Builder.

Cocoa e metafore di interfaccia grafica
Cocoa trova soluzioni per le seguenti metafore:

  • gestione finestre
  • menu statici vs dinamici
  • undo e redo
  • drag & drop (vedi protocollo NSTableViewDataSource)

Cocoa, Applicazioni e Interface Builder
Nell’architettura complessiva di un’applicazione Cocoa entra in gioco anche il concetto di “bundle”.
L’applicazione MacOSX è un insieme (bundle, per l’appunto) di risorse: non solo il codice eseguibile, ma anche descrizioni formali dell’interfaccia, immagini, dati, ecc.
La costruzione di questo insieme è demandata a XCode; nello specifico vendono posti insieme il prodotto della compilazione ed in particolare tutto quanto è compreso nella cartella Resource e del progetto corrente.

In particolare in questa cartella è presente un NIB (ad oggi in formato binario, dunque si parla di XIB, ma per quel che segue è del tutto equivalente) un archivio di informazioni prodotto dal programma Interface Builder che, come appare ovvio, riguarderanno la descrizione formale delle componenti di interfaccia grafica.
xib

In realtà la cosa è più generica: nella maggiorparte dei casi Interface Builder gestisce informazioni per l’interfaccia grafica, ma nei casi pratici può qualcosa di più.
Quando si parla di informazioni per l’interfaccia, in realtà si deve parlare propriamente di istanze di oggetti di classi Cocoa (in particolare di ApplicationKit) capaci di astrarre concetti come finestra, menu, casella di testo, ecc.
Interface Builder dunque istanzia (crea) oggetti di interfaccia: la descrizione dell’interfaccia dunque altro non è che un “contenitore di istanze di oggetti”, un concetto che apparentemente sembra una cosa strana ma che è semplicemente spiegabile introducendo il pattern di programmazione della serializzazione di oggetti.
Interface Builder istanzia dunque oggetti Cocoa (ogni icona nella sua finestra che elenca il contenuto di un NIB è una istanza) mentre l’utente programatore “disegna” l’interfaccia; in seguito li “serializza” nel file NIB/XIB.
Il processo di deserializzazione avviene alla partenza della applicazione che si ritroverà dunque già create tutte le istanze degli oggetti introdotti da Interface Builder.
A ciascun oggetto creato dal NIB verrà inviato il messaggio (leggi “verrà eseguito il metodo”) initWithCoder: e awakeFromNib:.
Eventuali specializzazioni di oggetti (classi utente derivare da qualche classe del framework) potranno governare queste fasi implementando una loro versione di questi metodi.
Ogni icona in Interface Builder rappresenta una istanza di una certa classe.

La seguente icona
object
rappresenta ad esempio una istanza di una classe NSObject o di una versione personalizzata (in questo caso il tipo è da indicare esplicitamente nella finestra inspector di Interface Builder).

Questa è una possibilità di generare istanze di oggetti, anche utente, in maniera visuale e con nascita legata all’avvio dell’applicazione, e dunque esistenti “per costruzione”.

Allocazione di oggetti nel codice ObjC
Diversa la sorte di una istanza di oggetto creata da codice utente.
Tipicamente l’allocazione avviene per mezzo del messaggio statico alloc: inviato ad una classe, a cui segue il messaggio init: (semplice o in qualche variante con argomenti) inviato all’istanza ottenuta.

[[NSObject alloc] init];

Qualora le specializzazioni utente delle classi abbiano la necessità di istanziare oggetti a partire da un NIB e a partire da codice devono implementare entrambi i messaggi initWithCoder: che init: ed eventuali varianti.

Oggetti e messaggi
Gli oggetti Cocoa sono progettati per essere immersi in un circuito di invio reciproco di messaggi atti a costituire quella collaborazione tra oggetti che in object-oriented produce l’essenza di un programma.
E’ dunque importante conoscere, specie nell’ottica della specializzazione, significato e ordine di invio di ciascun messaggio.
Raramente sarà il nostro codice specializzato a inviare messaggi; più frequentemente dovremo analizzare e realizzare le specializzazioni in relazione alla catena di messaggi che “l’applicazione” (vista in una ottica di autonomia di funzionamento) invia alle sue componenti, anche in relazione ad interazione con l’utente (eventi).

Continua

cocoa_cup

Introduzione a Cocoa – Parte II

Cocoa e pattern di programmazione
Cocoa è un framework complesso. Nella sua progettazione ed implementazione sono utilizzate molteplici tecniche di progettazione ad oggetti.
Tra queste ricordiamo l’adozione dei seguenti pattern di programmazione:

  • Modello-Vista-Controller (MVC)
  • Delega
  • Delega di data source
  • Observer – KVC, KVO & binding
  • Notifica

L’adozione di tali modelli di programmazione è praticamente obbligatoria: nella adozione più semplice (senza coinvolgere il progetto di nuove classi) si esplicita nell’individuare specifici oggetti (anche realizzati da utente) capaci di essere attori per uno di questi ruoli.
Cerchiamo di specificare brevemente.

Delega
Il processo di delega è un modo che una istanza di oggetto ha per “traslare” l’implementazione di un suo comportamento (metodo) con il metodo omonimo appartenente ad un altro oggetto (anche di tipo diverso).
Il processo di delega è una forma di composizione (composizione dei comportamenti) tra oggetti.
delega

Una classe che prevede processo di delega per i suoi oggetti avrà un attributo indicante l’oggetto delegato (delegate). Per classi gestite con Interface Builder è sufficiente collegare l’attributo delegato di un oggetto ad un altro oggetto per stabilire la possibilità che il processo di delega abbia luogo.
Nel processo di delega, uno stesso oggetto delegato può essere delegato ad implementare più di un compito (metodo).
Vediamo come esempio quali metodi vengono delegati dalla classe NSTableView ad un suo oggetto delegato:

  • tableView:willDisplayCell:forTableColumn:row:
  • tableView:shouldSelectRow:
  • tableView:shouldSelectTableColumn:
  • selectionShouldChangeInTableView:
  • tableView:shouldEditTableColumn:row:

Modello-Vista-Controllo (MVC – Model/View/Controller)
Pattern di programmazione storico introdotto da SmallTalk e comune a tutti gli approcci ad oggetti alle interfacce grafiche (Java compreso).
Si fonda sulla separazione logica ed implementativa della rappresentazione dei dati dell’applicazione (modello), della componente visuale di interfaccia utente (vista) e della componente che coordina i due e costituisca tutto quanto è eventualmente procedurale nella applicazione (controller).
Una applicazione Cocoa è fortemente conforme al pattern MVC.
Non a caso il framework introduce classi e derivazioni da queste per rappresentare una implementazione delle componenti conforme al modello, con forme e comportamenti prestabiliti a governare le più disparate esigenze.
Per Vista e Controllo abbiamo:

  • NSView
  • NSController

Per il modello si possono usare tutte le classi conforme al modello “container” o “collection” quali:

  • NSArray
  • NSSet

oppure la classe NSManagedObject con l’adozione del framework Core Data (>= 10.4).
mvc

Composizione
La composizione è una tecnica di ADT (Abstract Type Definition) per la composizione delle informazioni degli oggetti. In progettazione object-oriented viene indicata come avente maggiori vantaggi dell’ereditarietà (si prefersce in sostanza descrivere i problemi con relazione ha-un invece che è-un, per ridurre la complessità della gerarchia di derivazione o eventuali effetti collaterali).
Questa filosofia è adottata anche da Cocoa.
Alcune istanze di classe dell’ApplicationKit esistono solo come elementi parte di classi più grandi come “aiuto all’implementazione”.
Un esempio classico è la NSTextFieldCell, che incapsulata in unica istanza in una NSTableView costituisce l’elemento che “disegna” tutte le celle della tabella, riutilizzata più volte dall’algoritmo di disegno della classe NSTableView.
In questo caso si ha anche una scelta architetturale particolare per cui non si incapsulano n istanze di celle per quante sono gli elementi della tabella (aumento di complessità), ma una sola istanza che non è un vero oggetto grafico (non deriva da NSView), ma è uno strumento che implementa il comportamento grafico del “disegnatore”.

Protocolli
Un protocollo è un modo alternativo alla derivazione per rendere possibile l’intoperabilità di classi molto differenti che devono rispondere ai medesimi messaggi.
L’insieme dei messaggi in comune, invece di essere una interfaccia, diviene un protocollo.
Un protocollo diviene così una interfaccia che qualsiasi classe può adottare in maniera indipendente dal progetto.
Esistono due tipi di protocolli in objc: formali ed informali.
Un protocollo informale è sostanzialmente una categoria di NSObject. Contrariamente ad una interfaccia, un protocollo informale non è da implementarsi obbligatoriamente. Prima che sia invocato un metodo, l’oggetto chiamante controlla se l’oggetto destinatario del messaggio implementa il metodo. Dato che i metodi opzionali di un protocollo sono stati introdotti solo nel Objective-C 2.0, i protocolli informali sono stati essenziali nel modo in cui le classi di Foundation e AppKit hanno implementato la delega..

NSObject protocols:

  • autorelease
  • class
  • conformsToProtocol:
  • description
  • hash
  • isEqual:
  • isKindOfClass:
  • isMemberOfClass:
  • isProxy
  • performSelector:
  • performSelector:withObject:
  • performSelector:withObject:withObject:
  • release
  • respondsToSelector:
  • retain
  • retainCount
  • self
  • superclass
  • zone

Un protocollo formale dichiara una lista di metodi che ci si aspetta utilizzata da una classe utente. Hanno una propria sintassi per dichiarazione, adozione e controllo di tipo.

protocollo_formale

Esempio particolare di protocollo: NSTableViewDataSource
Il metodo setDataSource: di una NSTableView vuole un oggetto conforme ad protocollo informale NSTableVewDataSource:

- (void)setDataSource:(id<NSTableViewDataSource>)anObject

L’oggetto che viene impostato come “sorgente dati” sarà in realtà un oggetto sottoposto a processo di delega che risponde al protocollo informale richiesto.

In particolare delega i seguenti compiti (che sono dettati dal protocollo):

Delegate Message Significato
numberOfRowsInTableView: Lettura valori
tableView:objectValueForTableColumn:row: Lettura valori
tableView:setObjectValue:forTableColumn:row: Impostazione valori
tableView:acceptDrop:row:dropOperation: Dragging
tableView:namesOfPromisedFilesDroppedAtDestination:forDraggedRowsWithIndexes: Dragging
tableView:validateDrop:proposedRow:proposedDropOperation: Dragging
tableView:writeRowsWithIndexes:toPasteboard: Dragging
tableView:sortDescriptorsDidChange: Ordinamento

Osservatori e Notifiche
L’Observer pattern è un design pattern utilizzato per tenere sotto controllo lo stato di diversi oggetti.

Observer
Un observer definisce una dipendenza uno-a-molti tra oggetti al fine di notificare e aggiornare automaticamente tutti gli oggetti dipendenti al cambiamento dello stato dell’oggetto osservato. Il pattern Observer è essenzialmente un modello “pubblica e sottoscrivi” in cui un soggetto e i suoi osservatori sono non strettamente accoppiati. La comunicazione può avvenire tra gli osservatori e gli osservati senza che l’uno sappia molto dell’altro.

Notifications
Il meccanismo di notifica in Cocoa implementa un sistema diffusione messaggi uno-a-molti in accoro con il Observer pattern. Gli oggetti di un programma aggiungono se stessi o altri oggetti ad una lista di osservatori per una o più notifiche, ciascuna delle quali viene identificata da una stringa (nome della notifica).
notification
Gli oggetti delegati vengono automaticamente registrati per ricevere messaggi corrispondenti alle notifiche.

Esempio: delegato di una NSTableView. Questi messaggi informano il delegato quando la selezione è stata modificata o quando una colonna viene mossa o ridimensinoata:

Delegate Message Notification
tableViewColumnDidMove: NSTableViewColumnDidMoveNotification
tableViewColumnDidResize: NSTableViewColumnDidResizeNotification
tableViewSelectionDidChange: NSTableViewSelectionDidChangeNotification
tableViewSelectionIsChanging: NSTableViewSelectionIsChangingNotification

 

Continua

cocoa_cup

Introduzione a Cocoa – Parte I

Con questo post vogliamo iniziare una serie di articoli più astratti sulla programmazione Cocoa, quasi un tutorial.

La programmazione in ambito nativo MacOSX si fonda su due pilastri: Objective-C e Cocoa Framework. Questo documento non si prefigge lo scopo di coprire lo studio particolareggiato di queste tecnologie (non potrebbe), ma di fornire un rapido accesso allo sviluppo software MacOSX.
Per poter governare la programmazione in ambito MacOSX occorre conoscere inoltre gli strumenti XCode, ivi compresa la componente Interface Builder per lo sviluppo delle interfacce grafiche (prima di Xcode 4 era una applicazione esterna).
La programmazione Objective-C è una programmazione multi-paradigma: l’Objective-C (da adesso objc) si fonda sul linguaggio C (programmazione procedurale) e costruisce su questo una sovrastruttura per il paradigma Object-Oriented (con l’introduzione di specifiche sintassi).
Per l’implementazione del paradigma ad oggetti, l’objc introduce un runtime environment che costituisce un “motore” (utilizzato da ogni applicazione costruita in objective-c) atto a realizzare la metafora dei messaggi e la componente dinamica del modello ad oggetti del linguaggio (polimorfismo, dynamic binding, ecc).

L’adozione del modello ad oggetti viene supportata da alcuni framework: primo tra tutti Foundation (Origina da NextStep Foundation library), responsabile della costruzione organica del modello a partire dalla classe NSObject.

  • Tutte le classi di questo framework hanno nomi con prefisso “NS”, che intende ricordare l’origine in NextStep.

Nel contesto grafico MacOSX il principale framework adottato è Cocoa: questo estende Foundation con l’adozione di tutto un insieme di framework tematici. Anche in questo caso le classi hanno nomi con prefisso “NS”.
Il corollario di framework che costituiscono Cocoa si posizionano su diversi livelli di una gerarchia di astrazione delle componenti del S.O. e altro: ogni framework è dato per semplificare e riutilizzare un progetto relativo a situazioni specifiche (Core Audio, Core Graphics, Core Animation, Core Data, ecc).

  • Le classi appartenenti a questi framework possono essere riconosciute sempre attraverso la convenzione dei prefissi, ma non in senso rigoroso (es. quelle Core Data hanno esempio prefisso “NS”, mentre Core Graphics ha “CG”).
  • La forma dei prefissi è legata a fattori storici e gerarchici.

Tutti questi framework sono da riferirsi ad uno SDK relativo alla versione software del sistema operativo: un SDK ha un numero di versione pari al numero di versione del S.O. (es. SDK per Tiger è 10.4, per Leopard è 10.5, per Snow Leopard 10.6, per Lion 10.7, Mountain Lion 10.8, Mavericks 10.9).

API Compliance
Un’applicazione può essere compilata solo per uno specifico SDK (selezione che avviene nella configurazione del “target” in XCode); si può adottare una programmazione parametrica (ovvero con direttive condizionali di preprocessing) al fine di modellare il codice per applicare modifiche atte a compilare per specifico SDK e modulare le eventuali differenze rispetto ad un altro.

#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
// codice per Leopard e oltre
#else
// codice pre-Leopard
#endif

ABI Compliance
Un’applicazione può essere invero compilata per architetture differenti (PPC vs Intel): questo è l’adozione dei concetti di Universal Binary.

  • In questo caso Lion ha creato uno spartiacque: la soppressione di Rosetta e l’impossibilità di sviluppare codice per PPC ha impedito lo sviluppo in un unico progetto per più piattaforme.

Cocoa e GNUStep
GNUStep è una versione “open” di NextStep, e per questo ha molte similitudini con Cocoa.
Un codice realizzato in Cocoa è “riutilizzabile” in contesto GNUStep. Gli strumenti di sviluppo di GNUStep sono però gli originali Project Builder e Interface Builder di NextStep e divergono dunque per alcuni aspetti dalle novità introdotte da MacOSX e da XCode.
Riutilizzare il codice è possibile ma occorre riportarlo all’interno di un progetto GNUStep.
Il vincolo maggiore è il livello di compatibilità rispetto ai vari SDK disponibili nel mondo MacOSX: attualmente GNUStep è paragonabile a Panther, ossia 10.3. Un software calibrato per 10.4 oppure adottante framework particolari come Core Data, Core Audio o Core Animation, difficilmente saranno “portabili” nel mondo GNUStep.
Lo saranno solo se soluzioni a quelle differenze vengano prodotte dal progetto GNUStep stesso oppure in modo autonomo da chi sviluppa il software.

Cocoa runtime environment
Possiamo parlare di un vero e proprio runtime environment anche nel caso del framework Cocoa.
Il concetto di “applicazione” è proprio del framework, il quale offre un suo modello e una implementazione. In particolare il progetto riguardante il concetto di applicazione è parte dello specifico framework ApplicationKit.
Possiamo così leggittimamente parlare di “applicazioni Cocoa” contro “applicazioni BSD“, benchè in entrambi i casi si possa adottare objc e Foundation.
Cocoa suddivide il concetto di “applicazione” in due possibili modelli alternativi:

  • applicazione
  • applicazione basate su documento

Applicazione
Una applicazione incentrata su un ciclo di eventi che pongono in relazione gli elementi grafici presenti (finestre, menu, ecc).

Applicazione basata su document
Una applicazione il cui fulcro è un “gestore di documenti”; ciascun documento ha una propria finestra di interfaccia e una propria base dati di riferimento. Ciò che viene implementato in riferimento ad un documento astratto viene replicato (nei comportamenti) per ciascun docuento “aperto”.

Entry point di un programma Cocoa
Proprio in ragione della presa in carico da parte del framework del progetto “applicazione”, non esiste un “corpo principale del programma” come in altri ambienti di sviluppi.
Per tutte le applicazioni Cocoa si ha un unico “punti di inizio del programm” che è l’esecuzione della funzione NSApplicationMain(). Questo viene precostruito dal progetto modello in XCode.

int main(int argc, char *argv[])
{
return NSApplicationMain(argc,  (const char **) argv);
}
  • Ovviamente è data la possibilità di interferire con le caratteristiche base dell’applicazione (intesa come flusso principale di programma) attraverso alcune classi di ApplicationKit (utilizzando metodi e attributi di classe specifici) e implementando una versione ad-hoc della funzione NSApplicationMain().

Classi che riguardano l’applicazione sono: NSApplication, NSBundle, NSApp.
La complessità dell’intera architettura è tale che non può essere ripercorsa da questo documento introduttivo.

Continua.

core_midi_devel

Core MIDI e il tempo

Probabilmente Core Midi, parte dell’infrastruttura software Core Audio per OSX e iOS, è il software meno documentato da Apple e dal web in generale.
Volendo costruire un mini player MIDI integrato (all’interno di un programma più complesso) ho dovuto accontentarmi di un paio di esempi nella documentazione ADC (PlaySequence e PlaySoftMIDI) e i references dell’API, quasi tutta basata su strutture opache.

Costruire un modello funzionante non è stato molto difficile seguendo il prototipo PlaySenquence, apportando anche qualche modifica (ad esempio rimuovendo l’utilizzo delle classi C++ PublicUtility incluse nel progetto a favore di un paio di funzioni per la gestione del tempo di sistema).

Il problema è nato nel momento in cui ho voluto porre nell’interfaccia l’informazione del tempo corrente del brano in esecuzione con MusicPlayer (elemento dell’API).

Interrompiamo il racconto per una breve digressione sul tema temporizzazioni in ambito musicale/MIDI.

In musica il tempo si esprime tenendo presenti due parametri: la misura (ingl. bar) e la velocità.
Il tempo della misura (ingl. time signature) è un concetto più notazionale che pratico, ma che in sostanza caratterizza la collocazione reciproca delle note sull’asse tempo (e la loro durata) nel metro di riferimento adottato, ossia la misura): è un riferimento relativo capace di esprimere la struttura metrica del brano. La divisione viene indicata con una frazione che esprime quante note di quarto (ingl. quarter note; è l’unità di misura della divisione) compongono una misura (ingl. measure).

La velocità indica quanto veloci si susseguono le note. Nella notazione classica sono i riferimenti a quel “Piano”, “Pianissimo”, “Vivace”,con brio“, ecc. Con riferimento al metronomo e nella musica digitale si utilizza invece come parametro la velocità con cui si susseguono le note di quarto nel tempo, ossia quante note di quarto sono presenti in un minuto (beat per minute = BPM): è una misura assoluta e determina la velocità di esecuzione di un brano, qualsiasi metro si adotti.

Nel file MIDI (SMF = Standard Midi File) questi concetti sono espressi in vari modi. Il tempo della misura ad esempio è collocato con meta eventi nel flusso dei dati, così come la velocità di esecuzione, mentre posizione e durata delle note è espressa come differenziale tra due momenti espressi di una unità di misura estranea alla notazione tradizionale ma che funziona egregiamente come ponte tra questa e le necessità dell’informatica: lo step (o tick o, come lo definisce Core Midi, subbeat).
Lo step è una suddivisione della nota di quarto espressa a partire dal concetto di risoluzione, un concetto estraneo alla notazione classica, ma necessario a rendere la relazione tra il tempo assoluto (in milli o microsencondi, dipendentemente dal timer adottato dal software) e la nota di quarto; più propriamente uno step è la più piccola unità riconoscibile all’interno di una nota di quarto a partire da una risoluzione. Una risoluzione di 960 (quella adottata per esempio da Garage Band) indica che nella nota di quarto sono riconoscibili 960 frazioni di tempo (480 per la nota di ottavo, 240 per la nota di sedicesimo, ecc).
Quindi la risoluzione caratterizza la precisione con cui un software può esprimere le posizioni e le durate delle note (andando ben oltre la notazione tradizionale e consentendo così di esprimere quei discostamenti dalla divisione che caratterizza l’esecuzione umana di un brano). Vedi concetto di quantizzazione/umanizzazione.

E veniamo al problema in Core Midi.

Il discorso che segue fa riferimento all’API a partire da OSX 10.5 (e quindi anche iOS della stessa epoca).
L’API mette a disposizione (per la lettura di un SMF, ma anche per la generazione ex-novo) una struttura opaca MusicSequence che contiene una lista di strutture opache MusicTrack, più un accessorio di strutture e API di gestione (come il MusicEventIterator), il tutto a favore di un altro elemento opaco (MusicPlayer) capace di eseguire un brano espresso mediante un MusicSequence.
La funzione MusicSequenceFileLoad (MusicSequence References) costruisce una sequenza a partire da un SMF; nella MusicSequence le informazioni di cui abbiamo parlato in precedenza (e di cui certamente il file SMF ne ha una rappresentazione) vengono distribuite in un modo particolare e scarsamente documentato. L’unico modo di accedere alle informazioni dovrebbe essere attraverso l’API associata (strutture opache, ricordate?) ma tutto non è ben cordinato, specialmente nella documentazione.

La velocità di esecuzione del brano (ad esempio) è collocata in un dizionario leggibile mediante la funzione MusicSequenceGetInfoDictionary per la chiave “tempo”; le sue variazioni però devono essere rilevate (lo fa ovviamente anche MusicPlayer quando esegue il brano) nella “traccia tempo“, una traccia speciale in cui sono collocati (che lo siano stai nel file SMF o no) tutti i meta eventi che caratterizzazo il tempo.
La traccia tempo è ottenibile mediante la funzione MusicSequenceGetTempoTrack e iterabile mediante MusicEventIterator per leggerne e decodificarne ogni evento (se ad esempio il nostro software deve mostrare il contenuto di una MusicSequence invece di darla in pasto a MusicPlayer). Volendo usare MusicPlayer non saremo costretti a farlo.

Ma tornando al nostro problema di visualizzazione del tempo durante l’esecuzione con MusicPlayer, quello che dovremmo utilizzare è la funzione:

OSStatus MusicSequenceBeatsToBarBeatTime(
MusicSequence inSequence,
MusicTimeStamp inBeats,
UInt32 inSubbeatDivisor,
CABarBeatTime *outBarBeatTime
);

Questa dovrebbe essere intesa per mostrare il tempo (internamente espresso come numero float di note di quarto) nella canonica forma:

Misura:Battuta:Frammento di tempo

Per quanto riguarda il parametro inBeats, questo è facilmente ottenibile mediante MusicPlayerGetTime().
Per quanto riguarda la struttura CABarBeatTime del parametro di uscita outBarBeatTime non abbiamo documentazione; ma basta cercare in AudioToolbox/CoreAudioClock.h (erroneamente chiamato ancora nella documentazione AudioToolbox/CAClock.h ) per ottenere

struct CABarBeatTime {
SInt32 bar;
UInt16 beat;
UInt16 subbeat;
UInt16 subbeatDivisor;
UInt16 reserved;
};
typedef struct CABarBeatTime CABarBeatTime;

Ma per quanto riguarda il parametro inSubbeatDivisor non esiste documentazione.
Dopo varia sperimentazione intuisco che il parametro è in qualche modo associato al concetto di risoluzione. So che il file SMF pone questo valore nell’ultima parola a 16 bit del chunk THdr; ma non trovo nell’API di MusicSequence nulla che mi possa far accedere a questa informazione.
Solo una ricerca nei file AudioToolbox/CoreAudioClock.h mi conferma che il parametro subbeatDivisor è legato alla risoluzione: questo mi convince a cercare informazioni nell’API con una ricerca brutale del termine “risoluzione”.
Rinvengo una costante kSequenceTrackProperty_TimeResolution legata all’API di MusicTrack, e per la precizione alla funzione MusicTrackGetProperty e vengo a scoprire dalla documentazione che la sola traccia tempo detiene il dato di risoluzione del brano, esattamente quanto riportato in THrd.

Bingo ! Questo è il parametro necessario.

Vista la difficoltà nel reperire una informazione così vitale implemento una estensione all’API di MusicSequence (mi pare più appropriato) con il doppio fine di documentare e di semplificare l’utilizzo di questa informazione.

Ecco l’implementazione:

/**
* MusicSequenceGetResolution
*
* @author Andrea Tassotti
*
*/
OSStatus MusicSequenceGetResolution(MusicSequence inSequence, SInt16 *outResolution)
{
MusicTrack tempoTrack;

OSStatus status = MusicSequenceGetTempoTrack(inSequence, &tempoTrack);
if (status != noErr) return status;

UInt32 ioLength = sizeof(SInt16);
return MusicTrackGetProperty (
tempoTrack,
kSequenceTrackProperty_TimeResolution,
outResolution,
&ioLength
);
}

che sarà semplicemente utilizzabile nel seguente modo:

MusicTimeStamp time;
if ( MusicPlayerGetTime (player, &time) != noErr )
[NSException raise:@"getPrerollTime" format:@"Can't get time for player"];
if (time >= sequenceLength)
time = sequenceLength;

CABarBeatTime outBarBeatTime;
SInt16 resolution;

if ( MusicSequenceGetResolution(sequence, &resolution) != noErr )
resolution = 240;

if ( MusicSequenceBeatsToBarBeatTime(sequence, time, (UInt32)resolution, &outBarBeatTime) == noErr )
{
// Get the string representation of the current time (Garage Band style)
[timeTextField setStringValue: [NSString stringWithFormat:@"%04i.%2i.%2i.%03i",
outBarBeatTime.bar,
outBarBeatTime.beat,
outBarBeatTime.subbeat / (resolution/4) + 1, // 16-th
outBarBeatTime.subbeat ]];
}

Ovviamente in un caso reale (come nel mio software), la risoluzione verrà estratta una sola volta dalla traccia tempo.

Spero anche in questo caso di essere stato utile a qualcuno.

Il vice-versa, ovvero l’impostazione di una risoluzione per un file SMF si ottiene con la funzione che crea il file (questo è documentato):

OSStatus MusicSequenceFileCreate (
MusicSequence inSequence,
CFURLRef inFileRef,
MusicSequenceFileTypeID inFileType,
MusicSequenceFileFlags inFlags,
SInt inResolution);
);

Ma questa è una altra storia.

ios

NSMenu dinamici

Ecco a voi il primo nerdpost al cacao.

Oggi parliamo infatti di un argomento nuovo per nerdammer.it ma che non può mancare: programmazione OSX/iOS , da Cocoa a CocoaTouch.

Iniziamo oggi con una esperienza Cocoa: la classe NSMenu.

In generale è sempre stato un Nib/Xib a trasportare nelle nostre applicazioni la struttura dei menu (grazie a InterfaceBuilder): ma quanti non hanno mai avuto bisogno di costruire un menu totalmente dinamico? Continua a leggere