blocuri sunt de fapt o extensie a limbajului de programare C, dar sunt puternic utilizate de cadrul Objective-C al Apple. Acestea sunt similare cu lambda-urile C #, prin aceea că vă permit să definiți un bloc de declarații inline și să-l transmiteți altor funcții ca și când ar fi un obiect.
Prelucrarea datelor cu funcții vs. efectuarea acțiunilor arbitrare cu blocuriBlocurile sunt incredibil de convenabile pentru definirea metodelor de apel invers deoarece vă permit să definiți funcționalitatea dorită în punctul de invocare și nu în altă parte a programului. În plus, blocurile sunt implementate ca închiderile (la fel ca lambda-urile din C #), ceea ce face posibilă capturarea stării locale din jurul blocului, fără nici o muncă suplimentară.
Sintaxa blocurilor poate fi un pic tulburătoare în comparație cu sintaxa Obiectiv-C pe care am folosit-o în această carte, așa că nu vă faceți griji dacă va dura ceva timp pentru a fi confortabil cu ei. Vom începe prin a examina un exemplu simplu:
^ (int x) retur x * 2; ;
Aceasta definește un bloc care ia un parametru întreg, X
, și returnează valoarea înmulțită cu două. În afară de îngrijire (^
), aceasta se aseamănă cu o funcție normală: are o listă de parametri în paranteze, un bloc de instrucțiuni închis în brațele curbate și o valoare de returnare (opțională). În C #, acesta este scris ca:
x => x * 2;
Dar blocurile nu se limitează la expresii simple - ele pot conține un număr arbitrar de afirmații, la fel ca o funcție. De exemplu, puteți adăuga un NSLog ()
apelați înainte de a reveni la o valoare:
^ (int x) NSLog (@ "În jur să se înmulțească% i cu 2.", x); retur x * 2; ;
Dacă blocul dvs. nu ia nici un parametru, puteți omite total lista de parametri:
^ NSLog (@ "Acesta este un bloc destul de contrived."); NSLog (@ "Emite doar aceste două mesaje."); ;
Pe cont propriu, un bloc nu este tot atât de util. În mod obișnuit, le veți transmite unei alte metode ca funcție de retur. Aceasta este o caracteristică lingvistică foarte puternică, deoarece vă permite să vă tratați funcționalitate ca parametru, mai degrabă decât limitat la date. Puteți trece un bloc la o metodă, așa cum ați face orice altă valoare literală:
[anObject doSomethingWithBlock: ^ (int x) NSLog (@ "Înmulțirea% i cu două"); retur x * 2; ];
doSomethingWithBlock:
implementarea poate rula blocul la fel ca și în cazul unei funcții care deschide ușa unei mulțimi de noi paradigme organizaționale.
Ca un exemplu mai practic, să aruncăm o privire la sortUsingComparator:
metoda definită de NSMutableArray
. Aceasta oferă aceeași funcționalitate ca și sortedArrayUsingFunction:
metoda pe care am folosit-o în Tipuri de tipuri de date, cu excepția faptului că definiți algoritmul de sortare într-un bloc în locul unei funcții complete:
Eșantion de cod inclus: SortUsingBlock
#importint principal (int argc, const char * argv []) @autoreleasepool NSMutableArray * numere = [NSMutableArray arrayWithObjects: [NSNumber numberWithFloat: 3.0f], [NSNumber numberWithFloat: Numarul numarului NWWFloat: 12.2f], nil]; [numere sortUsingComparator: ^ NSComparisonResult (id obj1, id obj2) float number1 = [obj1 floatValue]; float numărul2 = [obj2 floatValue]; dacă (numărul1 < number2) return NSOrderedAscending; else if (number1 > numărul 2) return NSOrderedDescending; altceva return NSOrderedSame; ]; pentru (int i = 0; i<[numbers count]; i++) NSLog(@"%i: %0.1f", i, [[numbers objectAtIndex:i] floatValue]); return 0;
Din nou, acest lucru este un fel direct ascendent, dar posibilitatea de a defini algoritmul de sortare în același loc ca invocarea funcției este mai intuitivă decât să trebuiască să definiți o funcție independentă în altă parte a programului. De asemenea, observați că puteți declara variabilele locale într-un bloc la fel cum ați face și într-o funcție.
Cadrele standard Objective-C folosesc acest model de design pentru orice, de la sortare, enumerare la animație. De fapt, puteți chiar să înlocuiți for-loop-ul în ultimul exemplu cu NSArray
„s enumerateObjectsUsingBlock:
, așa cum se arată aici:
[sortNumbers enumerateObjectsUsingBlock: ^ (id id, NSUInteger idx, BOOL * stop) NSLog (@ "% lu:% 0.1f", idx, [obj floatValue]); dacă (idx == 2) // Opriți enumerarea la sfârșitul acestei iterații. * oprire = DA; ];
obj
parametru este obiectul curent, IDX
este indicele actual și *Stop
este o modalitate de a părăsi prematur enumerarea. Setarea *Stop
pointer la DA
spune metoda să nu mai enumere după iterația curentă. Toate aceste comportamente sunt specificate de enumerateObjectsUsingBlock:
metodă.
În timp ce animația este puțin subiectul pentru această carte, merită încă o scurtă explicație pentru a ajuta la înțelegerea utilității blocurilor. UIView
este una dintre cele mai utilizate clase în programarea iOS. Este un container grafic generic care vă permite să animați conținutul acestuia prin animateWithDuration: animații:
metodă. Al doilea parametru este un bloc care definește starea finală a animației, iar metoda determină automat modul de animare a proprietăților utilizând primul parametru. Acesta este un mod elegant, ușor de utilizat pentru a defini tranzițiile și alte comportamente bazate pe temporizator. Vom discuta despre animații cu mult mai multe detalii în viitor iOS Succinct carte.
În afară de trecerea la metode, blocurile pot fi stocate și în variabile pentru utilizare ulterioară. Acest caz de utilizare servește, în esență, ca o modalitate alternativă de a defini funcții:
#importint main (int argc, const char * argv []) @autoreleasepool int (^ addIntegers) (int, int); addIntegers = ^ (int x, int y) retur x + y; ; int rezultat = addIntegers (24, 18); NSLog (@ "% i", rezultat); retur 0;
Mai întâi, să inspectăm sintaxa pentru declararea variabilelor bloc: int (^ addIntegers) (int, int)
. Numele acestei variabile este pur și simplu addIntegers
(fără cartelă). Acest lucru poate fi confuz dacă nu ați folosit blocuri foarte mult timp. Ea ajută să se gândească la cartel ca versiunea bloc a operatorului de dereferență (*
). De exemplu, a ac indicator denumit addIntegers
ar fi declarat ca * addIntegers
-de asemenea, a bloc cu același nume este declarat ca ^ addIntegers
. Totuși, rețineți că aceasta nu este decât o similitudine superficială.
În plus față de numele variabilei, trebuie să declarați și toate metadatele asociate blocului: numărul de parametri, tipurile acestora și tipul de returnare. Acest lucru permite compilatorului să aplice siguranța tipului cu variabile de bloc. Rețineți că este îngrijit nu parte a numelui variabilei - este necesară numai în declarație.
Apoi, utilizăm operatorul de atribuire standard (=
) pentru a stoca un bloc în variabilă. Desigur, parametrii blocului ((int x, int y)
) trebuie să corespundă tipurilor de parametri declarate de variabila ((int, int)
). Un punct și virgulă este, de asemenea, necesar după definirea blocului, la fel ca o alocare normală a variabilei. Odată ce a fost populată cu o valoare, variabila poate fi numită exact ca o funcție: addIntegers (24, 18)
.
Dacă blocul dvs. nu ia nici un parametru, trebuie să declarați explicit acest lucru în variabilă prin plasare vid
în lista de parametri:
void (^ contrived) (void) = ^ NSLog (@ "Acesta este un bloc destul de contrived."); NSLog (@ "Emite doar aceste două mesaje."); ; contrived ();
Variabilele din interiorul blocurilor se comportă în același mod ca și în cazul funcțiilor normale. Puteți crea variabile locale în cadrul blocului, parametrii de acces transmiși blocului și puteți utiliza variabile și funcții globale (de ex., NSLog ()
). Dar, blocurile au, de asemenea, acces la variabilele non-locale, care sunt variabile din domeniul de aplicare lexical.
int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) returnează valoarea inițială + x; ; NSLog (@ "% i", addToInitialValue (10)); 42
În acest caz, valoarea initiala
este considerată o variabilă non-locală în cadrul blocului deoarece este definită în afara blocului (nu local, în raport cu blocul). Desigur, faptul că variabilele non-locale sunt doar pentru citire implică faptul că nu le puteți aloca:
int initialValue = 32; int (^ addToInitialValue) (int) = ^ (intx) initialValue = 5; // Aceasta va arunca o eroare de compilator. returneaza initialValue + x; ;
Accesul la variabilele din jur (non-locale) este o afacere importantă atunci când se utilizează blocuri inline ca parametri de metodă. Acesta oferă o modalitate convenabilă de a reprezenta orice stare cerută în cadrul blocului.
De exemplu, dacă animați culoarea unei componente UI și culoarea țintă a fost calculată și stocată într-o variabilă locală înainte de definirea blocului, ați putea pur și simplu să utilizați variabila locală în cadrul blocului - nu este necesară o lucrare suplimentară. Dacă nu ați avea acces la variabile non-locale, ați fi trecut valoarea de culoare ca parametru suplimentar de bloc. Când funcția de apel invers se bazează pe o mare parte a stării înconjurătoare, acest lucru poate fi foarte greoi.
Cu toate acestea, blocurile nu au doar acces la variabilele non-locale - se asigură, de asemenea, că aceste variabile vor nu schimbare, indiferent când sau în cazul în care blocul este executat. În majoritatea limbajelor de programare, acest lucru se numește a închidere.
Închiderea funcționează prin crearea unei copii constante, numai pentru citire, a oricăror variabile non-locale și stocarea acestora într-o tabelul de referință cu afirmațiile care alcătuiesc blocul propriu-zis. Aceste valori doar pentru citire sunt folosite de fiecare dată când blocul este executat, ceea ce înseamnă că, chiar dacă variabila inițială non-locală se modifică, valoarea utilizată de bloc este garantată a fi aceeași ca atunci când blocul a fost definit.
Stocarea variabilelor non-locale într-un tabel de referințăPutem vedea acest lucru în acțiune atribuindu - i o valoare nouă valoarea initiala
variabilă din exemplul anterior:
int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) returnează valoarea inițială + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42 initialValue = 100; NSLog (@ "% i", addToInitialValue (10)); // Încă 42.
Indiferent de unde chemați addToInitialValue ()
, valoarea initiala
utilizate de către bloc va mereu fi 32
, pentru că așa a fost atunci când a fost creat. Pentru toate intențiile și scopurile, este ca și cum valoarea initiala
variabila a fost transformată într-o valoare literală în interiorul blocului.
Deci, utilitatea blocurilor este de două ori:
Întreaga idee din spatele încapsulării funcționalității într-un bloc este să o poți folosi mai tarziu în program. Închiderea face posibilă asigurarea unui comportament previzibil oricând un bloc este executat prin înghețarea stării înconjurătoare. Acest lucru le face un aspect integral al programării blocurilor.
În majoritatea cazurilor, capturarea de stare cu închideri este intuitiv ceea ce v-ați aștepta de la un bloc. Există totuși momente care necesită un comportament opus. Variabile bloc blocabile sunt variabile non-locale care sunt desemnate citire-scriere în loc de citire standard numai pentru citire. Pentru a face o variabilă non-local variabilă, trebuie să o declarați cu __bloc
modificator, care creează o legătură directă între variabila folosită în afara blocului și cea utilizată în interiorul blocului. Aceasta deschide ușa utilizării blocurilor ca iteratori, generatoare și orice alt tip de obiect care procesează starea.
Următorul exemplu vă arată cum puteți face o variabilă non-locală mutable:
#import#import "Person.h" int principal (int argc, const char * argv []) @autoreleasepool __block NSString * nume = @ "Dave"; void (^ generateRandomName) (void) = ^ NSLog (@ "Schimbarea% @ la Frank", nume); nume = @ "Frank"; ; NSLog (@ "% @", nume); // Dave // Schimbă-l din interiorul blocului. generateRandomName (); Schimbarea lui Dave către Frank. NSLog (@ "% @", nume); // Frank // Schimbă-l din afara blocului. nume = @ "Heywood"; generateRandomName (); Schimbarea lui Heywood la Frank. retur 0;
Aceasta arată aproape exact la exemplul precedent, cu două diferențe foarte semnificative. În primul rând, non-locale Nume
variabil poate sa fi atribuit din interiorul blocului. În al doilea rând, schimbarea variabilei în afara blocului face actualizați valoarea folosită în cadrul blocului. Este posibil chiar să creați mai multe blocuri care manipulează toate aceleași variabile non-locale.
Singurul avertisment cu privire la utilizarea __bloc
modificator este că acesta nu poti să fie utilizate la matrice cu lungime variabilă.
Ar fi posibil, crearea de metode care acceptă blocuri este mai utilă decât stocarea lor în variabilele locale. Vă oferă posibilitatea de a adăuga propria dvs. enumerateObjectsUsingBlock:
-stil pentru clasele personalizate.
Luați în considerare următoarea interfață pentru Persoană
clasă:
// Person.h @interface Persoană: NSObject @property int age; - (void) celebrateBirthdayWithBlock: (void (^) (int)) activitate; @Sfârșit
void (^) (int)
codul este tipul de date pentru blocul pe care doriți să îl acceptați. În acest caz, vom accepta un bloc fără valoare de returnare și un singur parametru întreg. Observați că, spre deosebire de variabilele de bloc, acest lucru nu necesită un nume pentru bloc - doar un blocaj neadornat ^
caracter.
Acum aveți toate abilitățile necesare pentru a crea metode care acceptă blocurile ca parametri. O implementare simplă pentru Persoană
interfața afișată în exemplul precedent ar putea să pară ca:
// Person.mim #import "Person.h" @implementation Persoană @ vârsta de sinteză = _age; - (void) celebrateBirthdayWithBlock: (void (^) (int)) activitate NSLog (@ "Este un partid !!!"); Activitatea (self.age); @Sfârșit
Apoi, puteți trece o activitate personalizabilă pentru a efectua a Persoană
's ziua de nastere cum ar fi:
// main.m int main (int argc, const char * argv []) @autoreleasepool Persoana * dave = [[Person alloc] init]; dave.age = 37; [dave celebrateBirthdayWithBlock: ^ (vârstă int) NSLog (@ "Woot! Mă rotesc% i", vârsta + 1); ]; retur 0;
Ar trebui să fie clar că utilizarea blocurilor ca parametri este infinit mai flexibilă decât tipurile standard de date pe care le-am folosit până la acest capitol. De fapt, puteți spune o instanță do ceva, mai degrabă decât doar procesarea datelor.
Blocurile vă permit să reprezentați declarații ca obiecte Obiectiv-C, ceea ce vă permite să treceți arbitrar acţiuni la o funcție în loc să fie limitată la date. Acest lucru este util pentru orice, de la iterarea unei secvențe de obiecte la animarea componentelor UI. Blocurile sunt o extensie versatilă a limbajului de programare C și sunt un instrument necesar dacă intenționați să faceți o mulțime de lucruri cu cadrele standard iOS. În acest capitol, am învățat cum să creăm, să stocăm și să executăm blocuri, și am aflat despre complicațiile închiderilor și __bloc
modificator de stocare. Am discutat, de asemenea, câteva paradigme comune de utilizare pentru blocuri.
Astfel ne încheie călătoria prin obiectivul C. Am acoperit totul, de la sintaxa de bază, la principalele tipuri de date, clase, protocoale, proprietăți, metode, gestionarea memoriei, manipularea erorilor și chiar utilizarea avansată a blocurilor. Ne-am concentrat mai mult pe caracteristicile lingvistice decât pe crearea de aplicații grafice, dar aceasta a oferit o bază solidă pentru dezvoltarea aplicațiilor iOS. Până acum, sper că vă simțiți foarte confortabil cu limba Objective-C.
Rețineți că obiectivul C se bazează pe multe dintre aceleași concepte orientate pe obiect ca și alte limbi OOP. În timp ce am atins doar câteva modele de design orientate pe obiecte în această carte, practic toate paradigmele organizaționale disponibile altor limbi sunt, de asemenea, posibile în Obiectiv-C. Acest lucru înseamnă că puteți utiliza cu ușurință baza de cunoștințe existentă bazată pe obiecte cu instrumentele prezentate în capitolele anterioare.
Dacă sunteți gata să începeți să construiți aplicații iPhone și iPad funcționale, asigurați-vă că verificați a doua parte a acestei serii, iOS Succinct. Acest ghid practic pentru dezvoltarea aplicațiilor aplică toate competențele Obiectiv-C dobândite din această carte la situațiile de dezvoltare din lumea reală. Vom trece prin toate cadrele majore ale obiectivului C și vom învăța cum să realizăm sarcini de-a lungul drumului, printre care: configurarea interfețelor utilizatorilor, captarea datelor, desenarea grafică, salvarea și încărcarea fișierelor și multe altele.
Această lecție reprezintă un capitol din Obiectiv-C Succinct, o carte electronică gratuită de la echipa de la Syncfusion.