blokker er faktisk en utvidelse til C-programmeringsspråket, men de er tungt utnyttet av Apples mål-C-rammer. De ligner på C # s lambdas ved at de lar deg definere en blokk med uttalelser inline og overføre den til andre funksjoner som om det var en gjenstand.
Behandler data med funksjoner vs. utførelse av vilkårlig handling med blokkerBlokker er utrolig praktiske for å definere tilbakeringingsmetoder, siden de lar deg definere ønsket funksjonalitet ved anropspunktet i stedet for et annet sted i programmet. I tillegg er blokkene implementert som nedleggelser (akkurat som lambdas i C #), som gjør det mulig å fange den lokale staten rundt blokken uten ekstra arbeid.
Blokk syntaks kan være litt foruroligende sammenlignet med objektiv-C-syntaksen vi har brukt gjennom hele denne boken, så vær så bekymret hvis det tar litt tid å være komfortabel med dem. Vi starter med å se på et enkelt eksempel:
^ (int x) return x * 2; ;
Dette definerer en blokk som tar et heltallsparameter, x
, og returnerer den verdien multiplisert med to. Bortsett fra caret (^
), dette ligner en normal funksjon: den har en parameterliste i parentes, en instruksjonsblokk som er omsluttet i krøllete braces og en (valgfri) returverdi. I C # er dette skrevet som:
x => x * 2;
Men blokkene er ikke begrenset til enkle uttrykk, de kan inneholde et vilkårlig antall setninger, akkurat som en funksjon. For eksempel kan du legge til en NSLog ()
ring før du returnerer en verdi:
^ (int x) NSLog (@ "Om å multiplisere% jeg med 2.", x); returnere x * 2; ;
Hvis blokken din ikke tar noen parametre, kan du utelate parameterlisten helt og holdent:
^ NSLog (@ "Dette er en ganske konstruert blokk."); NSLog (@ "Det utsender bare disse to meldingene."); ;
På egenhånd er en blokk ikke så nyttig. Vanligvis sender du dem til en annen metode som tilbakeringingsfunksjon. Dette er en veldig kraftig språkfunksjon, som det lar deg behandle funksjonalitet som en parameter, i stedet for å være begrenset til data. Du kan sende en blokk til en metode som du ville ha en annen bokstavelig verdi:
[anObject doSomethingWithBlock: ^ (int x) NSLog (@ "Multiplikasjon% i med to"); returnere x * 2; ];
De doSomethingWithBlock:
implementering kan kjøre blokken akkurat som det ville drive en funksjon som åpner døren for mange nye organisatoriske paradigmer.
Som et mer praktisk eksempel, la oss ta en titt på sortUsingComparator:
metode definert av NSMutableArray
. Dette gir nøyaktig samme funksjonalitet som sortedArrayUsingFunction:
metode vi brukte i Datatyper, unntatt du definerer sorteringsalgoritmen i en blokk i stedet for en fullverdig funksjon:
Inkludert kodeeksempel: SortUsingBlock
#importereint main (int argc, const char * argv []) @ autoreleasepool NSMutableArray * numbers = [NSMutableArray arrayWithObjects: [NSNummer nummerWithFloat: 3.0f], [NSNummer nummerWithFloat: 5.5f], [NSNumber numberWithFloat: 1.0f], [ NSNummer nummerWithFloat: 12.2f], null]; [tall sortUsingComparator: ^ NSComparisonResult (id obj1, id obj2) float number1 = [obj1 floatValue]; float number2 = [obj2 floatValue]; hvis (nummer1 < number2) return NSOrderedAscending; else if (number1 > nummer2) return NSOrderedDescending; ellers return NSOrderedSame; ]; for (int i = 0; i<[numbers count]; i++) NSLog(@"%i: %0.1f", i, [[numbers objectAtIndex:i] floatValue]); return 0;
Igjen er dette en rett og slett stigende type, men å kunne definere sortalgoritmen på samme sted som funksjonen påkallingen er mer intuitiv enn å måtte definere en uavhengig funksjon andre steder i programmet. Legg også merke til at du kan erklære lokale variabler i en blokk som du ville i en funksjon.
Standardmål-C-rammene bruker dette designmønsteret for alt fra sortering, til oppsummering, til animasjon. Faktisk kan du selv erstatte for-loop i det siste eksemplet med NSArray
's enumerateObjectsUsingBlock:
metode, som vist her:
[sortedNumbers enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stopp) NSLog (@ "% lu:% 0.1f", idx, [obj floatValue]); hvis (idx == 2) // Stopp oppsummering på slutten av denne iterasjonen. * Stopp = JA; ];
De obj
parameter er det nåværende objektet, idx
er den nåværende indeksen, og *Stoppe
er en måte å avslutte opptellingen tidlig. Innstilling av *Stoppe
pekeren til JA
forteller metoden for å stoppe oppregningen etter gjeldende iterasjon. Alle denne oppførselen er spesifisert av enumerateObjectsUsingBlock:
metode.
Selv om animasjon er litt off-topic for denne boken, er det fortsatt verdt en kort forklaring å bidra til å forstå bruken av blokker. UIView
er en av de mest brukte klassene i IOS programmering. Det er en generisk grafisk beholder som lar deg animere innholdet via animateWithDuration: animasjoner:
metode. Den andre parameteren er en blokk som definerer den endelige tilstanden til animasjonen, og metoden viser automatisk hvordan man kan animere egenskapene ved hjelp av den første parameteren. Dette er en elegant, brukervennlig måte å definere overganger og annen timerbasert oppførsel på. Vi diskuterer animasjoner i mye mer detalj i kommende iOS Succinctly bok.
Bortsett fra at de overføres til metoder, kan blokker også lagres i variabler for senere bruk. Denne brukstilstanden tjener hovedsakelig som en alternativ måte å definere funksjoner på:
#importereint main (int argc, const char * argv []) @ autoreleasepool int (^ addIntegers) (int, int); addIntegers = ^ (int x, int y) return x + y; ; int resultat = addIntegers (24, 18); NSLog (@ "% i", resultat); returnere 0;
Først, la oss inspisere syntaksen for å erklære blokkvariabler: int (^ addIntegers) (int, int)
. Navnet på denne variabelen er ganske enkelt addIntegers
(uten caret). Dette kan være forvirrende hvis du ikke har brukt blokker veldig lenge. Det hjelper å tenke på karmen som blokkens versjon av dereference operatøren (*
). For eksempel a pekeren kalt addIntegers
ville bli erklært som * addIntegers
-også a blokkere med samme navn erklæres som ^ addIntegers
. Vær imidlertid oppmerksom på at dette bare er en overfladisk likhet.
I tillegg til det variable navnet må du også deklarere alle metadataene som er knyttet til blokken: antall parametere, deres typer og returtype. Dette gjør det mulig for kompilatoren å håndheve type sikkerhet med blokkvariabler. Legg merke til at caret er ikke En del av variabelenavnet-det er bare nødvendig i erklæringen.
Deretter bruker vi standardoppdragsoperatøren (=
) for å lagre en blokk i variabelen. Selvfølgelig er blokkens parametre ((int x, int y)
) må samsvare med parametertypene som er oppgitt av variabelen ((int, int)
). En semikolon er også nødvendig etter blokkdefinisjonen, akkurat som en vanlig variabel oppgave. Når den er fylt med en verdi, kan variabelen kalles akkurat som en funksjon: addIntegers (24, 18)
.
Hvis blokken din ikke tar noen parametere, må du eksplisitt deklarere dette i variabelen ved å plassere tomrom
i parameterlisten:
void (^ contrived) (void) = ^ NSLog (@ "Dette er en ganske konstruert blokk."); NSLog (@ "Det utsender bare disse to meldingene."); ; contrived ();
Variabler inne i blokker oppfører seg på samme måte som de gjør i normale funksjoner. Du kan opprette lokale variabler i blokken, tilgangsparametere som sendes til blokken, og bruke globale variabler og funksjoner (f.eks., NSLog ()
). Men blokker har også tilgang til ikke-lokale variabler, hvilke er variabler fra det lexiske omfanget som er omsluttet.
int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42
I dette tilfellet, Opprinnelig verdi
regnes som en ikke-lokal variabel innenfor blokken fordi den er definert utenfor blokken (ikke lokalt, i forhold til blokken). Selvfølgelig innebærer det faktum at ikke-lokale variabler er skrivebeskyttet, at du ikke kan tilordne dem:
int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) initialValue = 5; // Dette vil kaste en kompilatorfeil. returner initialValue + x; ;
Å ha tilgang til de omliggende (ikke-lokale) variablene er en stor avtale når du bruker inline-blokker som metodeparametere. Det gir en praktisk måte å representere hvilken som helst stat som kreves innenfor blokken.
Hvis du for eksempel animerte fargen til en brukergrensesnitt og målfarge ble beregnet og lagret i en lokal variabel før blokkdefinisjonen, kan du bare bruke den lokale variabelen i blokken, uten ekstra arbeid som kreves. Hvis du ikke hadde tilgang til ikke-lokale variabler, ville du ha bestått fargeværdien som en ekstra blokkparameter. Når tilbakeringingsfunksjonen er avhengig av en stor del av omgivelsesstaten, kan dette være svært tungvint.
Blokker har imidlertid ikke bare adgang til ikke-lokale variabler - de sikrer også at disse variablene vil aldri endre, uansett når eller hvor blokken blir utført. I de fleste programmeringsspråk kalles dette a nedleggelse.
Lukker fungerer ved å lage en konstant, skrivebeskyttet kopi av eventuelle ikke-lokale variabler og lagre dem i en referanse tabell med uttalelsene som utgjør selve blokken. Disse skrivebeskyttede verdiene brukes hver gang blokken utføres, noe som betyr at selv om den opprinnelige ikke-lokale variabelen endres, er verdien som brukes av blokken, den samme som den var da blokken ble definert.
Lagring av ikke-lokale variabler i en referansetabelVi kan se dette i aksjon ved å tildele en ny verdi til Opprinnelig verdi
variabel fra forrige eksempel:
int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42 initialValue = 100; NSLog (@ "% i", addToInitialValue (10)); // Still 42.
Uansett hvor du ringer addToInitialValue ()
, de Opprinnelig verdi
brukt av blokken vil alltid være 32
, fordi det var hva det var da det ble opprettet. For all hensikt er det som om Opprinnelig verdi
variabel ble forvandlet til en bokstavelig verdi inne i blokken.
Så er bruken av blokker to ganger:
Hele ideen bak innkapsling av funksjonalitet i en blokk er å kunne bruke den seinere i programmet. Lukking gjør det mulig å sikre forutsigbar oppførsel når som helst en blokk utføres ved å fryse omgivende tilstand. Dette gjør dem til et integrert aspekt av blokkprogrammering.
I de fleste tilfeller er fangsttilstand med lukninger intuitivt hva du kan forvente fra en blokk. Det er imidlertid tidspunkter som kaller motsatt oppførsel. Gjørbare blokkvariabler er ikke-lokale variabler som er utpekt lese-skrive i stedet for standard skrivebeskyttet. For å gjøre en ikke-lokal variabel gjensidig, må du deklarere den med __blokkere
modifikator, som skaper en direkte kobling mellom variabelen som brukes utenfor blokken og den som brukes inne i blokken. Dette åpner døren for å bruke blokker som iteratorer, generatorer og enhver annen type objekt som prosesser statliggjør.
Følgende eksempel viser hvordan du gjør en ikke-lokal variabel gjensidig:
#importere#import "Person.h" int main (int argc, const char * argv []) @ autoreleasepool __block NSString * navn = @ "Dave"; void (^ generateRandomName) (void) = ^ NSLog (@ "Endre% @ til Frank", navn); navn = @ "frank" ; NSLog (@ "% @", navn); // Dave // Endre det fra innsiden av blokken. generateRandomName (); // Endre Dave til Frank. NSLog (@ "% @", navn); // Frank // Endre det fra utsiden av blokken. navn = @ "Heywood"; generateRandomName (); // Bytter Heywood til Frank. returnere 0;
Dette ser nesten ut som det forrige eksempelet, med to svært betydelige forskjeller. Først, ikke-lokal Navn
variabel kan tildeles fra blokken. For det andre, endre variabelen utenfor blokken gjør oppdater verdien som brukes i blokken. Det er også mulig å lage flere blokker som alle manipulerer den samme ikke-lokale variabelen.
Den eneste advarselen til å bruke __blokkere
Modifier er det kan ikke brukes på variabel lengde arrays.
Å skape metoder som aksepterer blokker, er kanskje mer nyttig enn å lagre dem i lokale variabler. Det gir deg muligheten til å legge til din egen enumerateObjectsUsingBlock:
-stil metoder til egendefinerte klasser.
Vurder følgende grensesnitt for Person
klasse:
// Person.h @ Interface Person: NSObject @property int age; - (ugyldig) feireBirthdayWithBlock: (void (^) (int)) aktivitet; @slutt
De void (^) (int)
kode er datatypen for blokken du vil godta. I dette tilfellet aksepterer vi en blokk uten returverdi og et enkelt heltallsparameter. Legg merke til at dette, i motsetning til blokkvariabler, ikke krever et navn for blokken - bare en uklart ^
karakter.
Du har nå alle ferdigheter som er nødvendige for å lage metoder som aksepterer blokker som parametere. En enkel implementering for Person
grensesnittet vist i forrige eksempel kan se ut som:
// Person.m #import "Person.h" @implementation Person @synthesize age = _age; - (void) celebrateBirthdayWithBlock: (void (^) (int)) aktivitet NSLog (@ "Det er en fest !!!"); aktivitet (self.age); @slutt
Deretter kan du passere en tilpassbar aktivitet for å utføre på en Person
s bursdag slik:
// main.m int main (int argc, const char * argv []) @autoreleasepool Person * dave = [[Personallokering] init]; dave.age = 37; [dave celebrateBirthdayWithBlock: ^ (int alder) NSLog (@ "Woot! Jeg setter% i", alder + 1); ]; returnere 0;
Det skal være tydelig at bruk av blokker som parametere er uendelig mer fleksible enn standard datatyper vi har brukt opp til dette kapittelet. Du kan faktisk fortelle en forekomst til gjøre noe, i stedet for bare å behandle data.
Blokker lar deg representere uttalelser som Objective-C objekter, som gjør at du kan passere vilkårlig handlinger til en funksjon i stedet for å være begrenset til data. Dette er nyttig for alt fra iterating over en sekvens av objekter til animerende UI-komponenter. Blokker er en allsidig forlengelse til C-programmeringsspråket, og de er et nødvendig verktøy hvis du planlegger å gjøre mye arbeid med standard iOS-rammer. I dette kapittelet lærte vi hvordan å lage, lagre og utføre blokker, og vi lærte om vanskelighetene med nedleggelser og __blokkere
lagringsmodifikator. Vi diskuterte også noen vanlige bruksparadigmer for blokker.
Dermed avsluttes vår reise gjennom Mål-C. Vi har dekket alt fra grunnleggende syntaks til kjerne datatyper, klasser, protokoller, egenskaper, metoder, minnehåndtering, feilsøking og til og med avansert bruk av blokker. Vi fokuserte mer på språkfunksjoner enn å lage grafiske applikasjoner, men dette ga et solid grunnlag for iOS app-utvikling. Nå håper jeg at du føler deg veldig komfortabel med Objective-C-språket.
Husk at Objective-C bygger på mange av de samme objektorienterte konseptene som andre OOP-språk. Mens vi bare rørte på noen objektorienterte designmønstre i denne boken, er praktisk talt alle de organisatoriske paradigmene som er tilgjengelige for andre språk, også mulige i Objective-C. Dette betyr at du enkelt kan utnytte din eksisterende objektorienterte kunnskapsbase med verktøyene som presenteres i de foregående kapitlene.
Hvis du er klar til å begynne å bygge funksjonelle iPhone- og iPad-applikasjoner, sørg for å sjekke ut den andre delen av denne serien, iOS Succinctly. Denne praktiske veiledningen til app-utvikling gjelder alle Objektiv-C-ferdighetene som er oppnådd fra denne boken, til virkelige utviklingssituasjoner. Vi går gjennom alle de store mål-C rammene og lærer å utføre oppgaver underveis, blant annet: konfigurere brukergrensesnitt, fange innspill, tegne grafikk, lagre og laste filer, og mye, mye mer.
Denne leksjonen representerer et kapittel fra Objective-C Succinctly, en gratis eBok fra teamet på Syncfusion.