Obiectiv-C Succinct Blocuri

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 blocuri

Blocurile 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ă.


Crearea blocurilor

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; ;

Parametrul-mai puține blocuri

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."); ;

Utilizarea blocurilor ca apeluri de apel

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

#import  int 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.


Stocarea și executarea blocurilor

Î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:

#import  int 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).

Parametrul-mai puține blocuri variabile

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 ();

Lucrul cu variabilele

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.

Blocurile sunt închise

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:

  1. Acestea vă permit să reprezentați funcționalitatea ca obiect.
  2. Ele vă permit să reprezentați informații de stat alături de această funcție.

Î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.

Variabile blocabile variabile

Î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.

Crearea unei legături directe cu o variabilă bloc blocabilă

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ă.

Definirea metodelor care acceptă blocurile

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.


rezumat

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.


iOS Succinct

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.