blocchi sono in realtà un'estensione del linguaggio di programmazione C, ma sono pesantemente utilizzati dai framework Objective-C di Apple. Sono simili ai lambda di C # in quanto consentono di definire un blocco di istruzioni in linea e di passarlo ad altre funzioni come se fosse un oggetto.
Elaborazione di dati con funzioni rispetto all'effettuazione di azioni arbitrarie con blocchiI blocchi sono incredibilmente convenienti per definire i metodi di callback poiché consentono di definire la funzionalità desiderata al momento dell'invocazione piuttosto che da qualche altra parte nel programma. Inoltre, i blocchi sono implementati come chiusure (proprio come lambda in C #), che rende possibile catturare lo stato locale che circonda il blocco senza alcun lavoro extra.
La sintassi dei blocchi può essere un po 'inquietante rispetto alla sintassi Objective-C che abbiamo usato in questo libro, quindi non preoccuparti se ci vuole un po' per sentirsi a proprio agio con loro. Inizieremo guardando un semplice esempio:
^ (int x) return x * 2; ;
Questo definisce un blocco che accetta un parametro intero, X
, e restituisce quel valore moltiplicato per due. A parte il segno di omissione (^
), assomiglia a una funzione normale: ha un elenco di parametri tra parentesi, un blocco di istruzioni racchiuso tra parentesi graffe e un valore di ritorno (facoltativo). In C #, questo è scritto come:
x => x * 2;
Ma i blocchi non sono limitati alle espressioni semplici: possono contenere un numero arbitrario di istruzioni, proprio come una funzione. Ad esempio, è possibile aggiungere un NSLog ()
chiama prima di restituire un valore:
^ (int x) NSLog (@ "Informazioni su moltiplicare% i per 2.", x); return x * 2; ;
Se il tuo blocco non accetta alcun parametro, puoi omettere del tutto l'elenco dei parametri:
^ NSLog (@ "Questo è un blocco abbastanza forzato."); NSLog (@ "Emette solo questi due messaggi."); ;
Di per sé, un blocco non è così utile. In genere, li passerai a un altro metodo come funzione di callback. Questa è una funzionalità linguistica molto potente, in quanto ti consente di trattare funzionalità come parametro, piuttosto che essere limitato a dati. Puoi passare un blocco a un metodo come qualsiasi altro valore letterale:
[anObject doSomethingWithBlock: ^ (int x) NSLog (@ "Moltiplicando% i per due"); return x * 2; ];
Il doSomethingWithBlock:
l'implementazione può eseguire il blocco come se fosse una funzione, che apre la porta a molti nuovi paradigmi organizzativi.
Come esempio più pratico, diamo un'occhiata al sortUsingComparator:
metodo definito da NSMutableArray
. Questo fornisce la stessa identica funzionalità di sortedArrayUsingFunction:
metodo che abbiamo usato nel Capitolo Tipi di dati, eccetto che si definisce l'algoritmo di ordinamento in un blocco invece di una funzione a pieno titolo:
Esempio di codice incluso: SortUsingBlock
#importareint main (int argc, const char * argv []) @autoreleasepool NSMutableArray * numbers = [NSMutableArray arrayWithObjects: [NSNumero numeroWithFloat: 3.0f], [NSNumero numeroWithFloat: 5.5f], [NSNumero numeroWithFloat: 1.0f], [ NSNumber numberWithFloat: 12.2f], nil]; [numbers sortUsingComparator: ^ NSComparisonResult (id obj1, id obj2) float number1 = [obj1 floatValue]; float number2 = [obj2 floatValue]; se (numero1 < number2) return NSOrderedAscending; else if (number1 > numero2) return NSOrderedDescending; else return NSOrderedSame; ]; per (int i = 0; i<[numbers count]; i++) NSLog(@"%i: %0.1f", i, [[numbers objectAtIndex:i] floatValue]); return 0;
Ancora una volta, questo è un ordinamento ascendente diretto, ma essere in grado di definire l'algoritmo di ordinamento nello stesso posto dell'invocazione della funzione è più intuitivo rispetto alla necessità di definire una funzione indipendente altrove nel programma. Si noti inoltre che è possibile dichiarare variabili locali in un blocco esattamente come si farebbe in una funzione.
I framework standard Objective-C utilizzano questo modello di progettazione per tutto, dall'ordinamento, all'enumerazione, all'animazione. In effetti, potresti anche sostituire il ciclo for nell'ultimo esempio con NSArray
'S enumerateObjectsUsingBlock:
metodo, come mostrato qui:
[sortedNumbers enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) NSLog (@ "% lu:% 0.1f", idx, [obj floatValue]); if (idx == 2) // Interrompi l'enumerazione alla fine di questa iterazione. * stop = SÌ; ];
Il obj
parametro è l'oggetto corrente, idx
è l'indice corrente e *Stop
è un modo per uscire prematuramente dall'enumerazione. Impostazione del *Stop
puntatore a SÌ
dice al metodo di interrompere l'enumerazione dopo l'iterazione corrente. Tutto questo comportamento è specificato dal enumerateObjectsUsingBlock:
metodo.
Mentre l'animazione è un po 'fuori tema per questo libro, vale ancora una breve spiegazione per aiutare a capire l'utilità dei blocchi. UIView
è una delle classi più utilizzate nella programmazione iOS. È un contenitore grafico generico che ti permette di animare il suo contenuto tramite il animateWithDuration: animazioni:
metodo. Il secondo parametro è un blocco che definisce lo stato finale dell'animazione e il metodo calcola automaticamente come animare le proprietà utilizzando il primo parametro. Questo è un modo elegante e intuitivo per definire le transizioni e altri comportamenti basati sul timer. Discuteremo delle animazioni in modo molto più dettagliato nell'imminente iOS in modo succinto libro.
Oltre a passare a metodi, i blocchi possono anche essere memorizzati in variabili per un uso successivo. Questo caso d'uso serve essenzialmente come un modo alternativo per definire le funzioni:
#importareint main (int argc, const char * argv []) @autoreleasepool int (^ addIntegers) (int, int); addIntegers = ^ (int x, int y) return x + y; ; int result = addIntegers (24, 18); NSLog (@ "% i", risultato); restituisce 0;
Innanzitutto, esaminiamo la sintassi per la dichiarazione delle variabili di blocco: int (^ addIntegers) (int, int)
. Il nome di questa variabile è semplicemente addIntegers
(senza il segno di omissione). Questo può essere fonte di confusione se non si utilizzano blocchi da molto tempo. Aiuta a pensare al punto di accesso come la versione del blocco dell'operatore di dereferenziazione (*
). Ad esempio, a pointer chiamato addIntegers
sarebbe dichiarato come * addIntegers
-allo stesso modo, a bloccare dello stesso nome è dichiarato come ^ addIntegers
. Tuttavia, tieni presente che questa è solo una somiglianza superficiale.
Oltre al nome della variabile, devi anche dichiarare tutti i metadati associati al blocco: il numero di parametri, i loro tipi e il tipo di ritorno. Ciò consente al compilatore di applicare la sicurezza del tipo con variabili di blocco. Si noti che il segno di omissione è non parte del nome della variabile: è richiesto solo nella dichiarazione.
Successivamente, utilizziamo l'operatore di assegnazione standard (=
) per memorizzare un blocco nella variabile. Ovviamente, i parametri del blocco ((int x, int y)
) deve corrispondere ai tipi di parametri dichiarati dalla variabile ((int, int)
). Un punto e virgola è richiesto anche dopo la definizione del blocco, proprio come una normale assegnazione di variabile. Una volta che è stato popolato con un valore, la variabile può essere chiamata esattamente come una funzione: addIntegers (24, 18)
.
Se il tuo blocco non accetta alcun parametro, devi dichiararlo esplicitamente nella variabile posizionandolo vuoto
nella lista dei parametri:
void (^ contrived) (void) = ^ NSLog (@ "Questo è un blocco abbastanza forzato."); NSLog (@ "Emette solo questi due messaggi."); ; artificiosa ();
Le variabili all'interno dei blocchi si comportano più o meno come nelle normali funzioni. È possibile creare variabili locali all'interno del blocco, accedere ai parametri passati al blocco e utilizzare variabili e funzioni globali (ad es., NSLog ()
). Ma i blocchi hanno anche accesso a variabili non locali, che sono variabili dall'ambito lessicale allegato.
int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42
In questo caso, valore iniziale
è considerata una variabile non locale all'interno del blocco perché è definita al di fuori del blocco (non localmente, relativamente al blocco). Naturalmente, il fatto che le variabili non locali siano di sola lettura implica che non è possibile assegnarle:
int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) initialValue = 5; // Questo genererà un errore del compilatore. return initialValue + x; ;
Avere accesso alle variabili circostanti (non locali) è un grosso problema quando si usano i blocchi in linea come parametri del metodo. Fornisce un modo conveniente per rappresentare qualsiasi stato richiesto all'interno del blocco.
Ad esempio, se si stava animando il colore di un componente dell'interfaccia utente e il colore target è stato calcolato e memorizzato in una variabile locale prima della definizione del blocco, è possibile utilizzare semplicemente la variabile locale all'interno del blocco, senza lavoro aggiuntivo richiesto. Se non avessi accesso a variabili non locali, avresti passato il valore del colore come parametro di blocco aggiuntivo. Quando la funzionalità di callback si basa su gran parte dello stato circostante, questo può essere molto ingombrante.
Tuttavia, i blocchi non hanno solo accesso a variabili non locali - assicurano anche che quelle variabili lo faranno mai cambiare, non importa quando o dove viene eseguito il blocco. Nella maggior parte dei linguaggi di programmazione, questo è chiamato a chiusura.
Le chiusure funzionano creando una copia costante di sola lettura di tutte le variabili non locali e memorizzandole in a tabella di riferimento con le affermazioni che costituiscono il blocco stesso. Questi valori di sola lettura vengono utilizzati ogni volta che il blocco viene eseguito, il che significa che anche se la variabile originale non locale cambia, il valore utilizzato dal blocco è garantito come lo era quando il blocco era definito.
Memorizzazione di variabili non locali in una tabella di riferimentoPossiamo vedere questo in azione assegnando un nuovo valore al valore iniziale
variabile dall'esempio precedente:
int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42 initialValue = 100; NSLog (@ "% i", addToInitialValue (10)); // Ancora 42.
Non importa dove chiami addToInitialValue ()
, il valore iniziale
usato dalla volontà del blocco sempre essere 32
, perché è quello che era quando è stato creato. A tutti gli effetti, è come se il valore iniziale
la variabile è stata trasformata in un valore letterale all'interno del blocco.
Quindi, l'utilità dei blocchi è duplice:
L'intera idea dietro l'incapsulamento della funzionalità in un blocco è quella di poterlo usare dopo nel programma. Le chiusure consentono di garantire un comportamento prevedibile ogni volta un blocco viene eseguito bloccando lo stato circostante. Questo li rende un aspetto integrante della programmazione dei blocchi.
Per la maggior parte dei casi, catturare lo stato con chiusure è intuitivamente ciò che ci si aspetterebbe da un blocco. Ci sono, tuttavia, tempi che richiedono il comportamento opposto. Variabili di blocco mutabili sono variabili non locali designate in lettura-scrittura anziché in sola lettura predefinita. Per rendere mutabile una variabile non locale, devi dichiararla con il __bloccare
modificatore, che crea un collegamento diretto tra la variabile utilizzata all'esterno del blocco e quella utilizzata all'interno del blocco. Questo apre la porta all'utilizzo di blocchi come iteratori, generatori e qualsiasi altro tipo di oggetto che elabora lo stato.
L'esempio seguente mostra come rendere mutabile una variabile non locale:
#importare#import "Person.h" int main (int argc, const char * argv []) @autoreleasepool __block NSString * name = @ "Dave"; void (^ generateRandomName) (void) = ^ NSLog (@ "Modifica% @ in Frank", nome); name = @ "Frank"; ; NSLog (@ "% @", nome); // Dave // Cambia da dentro il blocco. generateRandomName (); // Cambiare Dave in Frank. NSLog (@ "% @", nome); // Frank // Cambialo dall'esterno del blocco. name = @ "Heywood"; generateRandomName (); // Modifica di Heywood a Frank. restituisce 0;
Questo sembra quasi esattamente lo stesso dell'esempio precedente, con due differenze molto significative. Innanzitutto, il non locale nome
variabile può essere assegnato all'interno del blocco. Secondo, cambiando la variabile al di fuori del blocco fa aggiorna il valore utilizzato all'interno del blocco. È persino possibile creare più blocchi che manipolano la stessa variabile non locale.
L'unico avvertimento per usare il __bloccare
modificatore è così non può essere utilizzato su array di lunghezza variabile.
Probabilmente, la creazione di metodi che accettano blocchi è più utile della memorizzazione in variabili locali. Ti dà l'opportunità di aggiungere il tuo enumerateObjectsUsingBlock:
-metodi di stile per classi personalizzate.
Si consideri la seguente interfaccia per Persona
classe:
// Person.h @interface Persona: NSObject @property int age; - (void) celebrateBirthdayWithBlock: (void (^) (int)) attività; @fine
Il void (^) (int)
codice è il tipo di dati per il blocco che si desidera accettare. In questo caso, accetteremo un blocco senza valore di ritorno e un singolo parametro intero. Si noti che, a differenza delle variabili di blocco, questo non richiede un nome per il blocco, ma solo un disadorno ^
personaggio.
Ora disponi di tutte le competenze necessarie per creare metodi che accettano blocchi come parametri. Una semplice implementazione per il Persona
l'interfaccia mostrata nell'esempio precedente potrebbe sembrare qualcosa di simile:
// Person.m #import "Person.h" @implementation Person @synthesize age = _age; - (void) celebrateBirthdayWithBlock: (void (^) (int)) activity NSLog (@ "È una festa !!!"); attività (self.age); @fine
Quindi, è possibile passare un'attività personalizzabile da eseguire su a Persona
Il compleanno è così:
// main.m int main (int argc, const char * argv []) @autoreleasepool Person * dave = [[Person alloc] init]; dave.age = 37; [dave celebrateBirthdayWithBlock: ^ (int age) NSLog (@ "Woot! Sto trasformando% i", età + 1); ]; restituisce 0;
Dovrebbe essere immediatamente evidente che l'utilizzo di blocchi come parametri è infinitamente più flessibile rispetto ai tipi di dati standard che abbiamo utilizzato fino a questo capitolo. In realtà puoi dire a un'istanza di fare qualcosa, piuttosto che elaborare semplicemente i dati.
I blocchi ti consentono di rappresentare istruzioni come oggetti Objective-C, che ti consentono di passare arbitrariamente Azioni a una funzione invece di essere limitato a dati. Questo è utile per tutto, dall'iterazione di una sequenza di oggetti all'animazione dei componenti dell'interfaccia utente. I blocchi sono un'estensione versatile del linguaggio di programmazione C, e sono uno strumento necessario se hai intenzione di fare molto lavoro con i framework iOS standard. In questo capitolo, abbiamo imparato come creare, archiviare ed eseguire blocchi, e abbiamo imparato a conoscere le complessità delle chiusure e __bloccare
modificatore di archiviazione. Abbiamo anche discusso alcuni paradigmi di utilizzo comuni per i blocchi.
Così conclude il nostro viaggio attraverso l'obiettivo-C. Abbiamo coperto tutto, dalla sintassi di base ai tipi di dati di base, classi, protocolli, proprietà, metodi, gestione della memoria, gestione degli errori e persino l'uso avanzato dei blocchi. Ci siamo concentrati maggiormente sulle funzionalità linguistiche rispetto alla creazione di applicazioni grafiche, ma ciò ha fornito una solida base per lo sviluppo di app per iOS. Ormai spero ti senta molto a tuo agio con il linguaggio Objective-C.
Ricorda che Objective-C si basa su molti degli stessi concetti orientati agli oggetti degli altri linguaggi OOP. Mentre in questo libro abbiamo solo toccato alcuni schemi di progettazione orientati agli oggetti, praticamente tutti i paradigmi organizzativi disponibili per altre lingue sono possibili anche in Objective-C. Ciò significa che puoi facilmente sfruttare la tua conoscenza esistente orientata agli oggetti con gli strumenti presentati nei capitoli precedenti.
Se sei pronto per iniziare a costruire applicazioni iPhone e iPad funzionali, assicurati di controllare la seconda parte di questa serie, iOS in modo succinto. Questa guida pratica allo sviluppo di app applica tutte le abilità Objective-C acquisite da questo libro a situazioni di sviluppo reali. Passeremo attraverso tutti i principali framework Objective-C e impareremo come eseguire attività lungo il percorso, tra cui: configurazione delle interfacce utente, acquisizione di input, disegno di grafica, salvataggio e caricamento di file e molto altro ancora.
Questa lezione rappresenta un capitolo di Objective-C, un eBook gratuito del team di Syncfusion.