Objective-C Succinctly Block

Blocks är egentligen en förlängning till C-programmeringsspråket, men de används kraftigt av Apples mål-C-ramar. De liknar C # s lambdas genom att de låter dig definiera ett block av uttalanden inline och vidarebefordra det till andra funktioner som om det var ett objekt.

Bearbetar data med funktioner vs. utförande av godtyckliga åtgärder med block

Block är otroligt bekväma för att definiera återkallningsmetoder eftersom de låter dig definiera önskad funktionalitet vid anropspunkten snarare än någon annanstans i ditt program. Dessutom implementeras block som förslutningar (precis som lambdas i C #), vilket gör det möjligt att fånga den lokala staten kring kvarteret utan extra arbete.


Skapa block

Blocksyntax kan vara lite oroande jämfört med objektiv-C-syntaxen som vi har använt i hela den här boken, så oroa dig inte om det tar ett tag att vara bekväm med dem. Vi börjar med att titta på ett enkelt exempel:

^ (int x) return x * 2; ;

Detta definierar ett block som tar en heltalsparameter, x, och returnerar det värdet multiplicerat med två. Bortsett från caret (^), liknar detta en normal funktion: den har en parameterlista inom parentes, ett instruktionsblock omslutet i krökta hängslen och ett (valfritt) returvärde. I C # är detta skrivet som:

x => x * 2;

Men block är inte begränsade till enkla uttryck - de kan innehålla ett godtyckligt antal uttalanden, precis som en funktion. Till exempel kan du lägga till en NSLog () ring innan du returnerar ett värde:

^ (int x) NSLog (@ "Om att multiplicera% i med 2.", x); returnera x * 2; ;

Parameter-Mindre Blocks

Om ditt block inte tar några parametrar kan du helt och hållet släppa bort parameterlistan:

^ NSLog (@ "Detta är ett ganska konstruerat block."); NSLog (@ "Det matar bara ut dessa två meddelanden."); ;

Använda block som återuppringningar

På egen hand är ett block inte allt som är användbart. Vanligtvis överför du dem till en annan metod som återuppringningsfunktion. Detta är en mycket kraftfull språkfunktion, som det låter dig behandla funktionalitet som en parameter, snarare än att vara begränsad till data. Du kan skicka ett block till en metod som du skulle ha något annat bokstavligt värde:

[anObject doSomethingWithBlock: ^ (int x) NSLog (@ "Multiplicera% i med två"); returnera x * 2; ];

De doSomethingWithBlock: genomförandet kan springa blocket precis som det skulle driva en funktion som öppnar dörren till många nya organisatoriska paradigmer.

Som ett mer praktiskt exempel, låt oss ta en titt på sortUsingComparator: metod definierad av NSMutableArray. Detta ger exakt samma funktionalitet som sortedArrayUsingFunction: metod vi använde i Kapitlet Datatyper, förutom att du definierar sorteringsalgoritmen i ett block istället för en fullfjädrad funktion:

Inkluderat kodprov: SortUsingBlock

#importera  Int main (int argc, const char * argv []) @ autoreleasepool NSMutableArray * numbers = [NSMutableArray arrayWithObjects: [NSNumber numberWithFloat: 3.0f], [NSNumber numberWithFloat: 5.5f], [NSNumber numberWithFloat: 1.0f], [ NSNummernummerWithFloat: 12.2f], nil]; [antal sortUsingComparator: ^ NSComparisonResult (id obj1, id obj2) float number1 = [obj1 floatValue]; float number2 = [obj2 floatValue]; om (nummer1 < number2)  return NSOrderedAscending;  else if (number1 > nummer2) returnera NSOrderedDescending;  annars returnera NSOrderedSame; ]; för (int i = 0; i<[numbers count]; i++)  NSLog(@"%i: %0.1f", i, [[numbers objectAtIndex:i] floatValue]);   return 0; 

Återigen är detta en rak uppstigande sort, men att kunna definiera sortalgoritmen på samma plats som funktionskallelsen är mer intuitiv än att behöva definiera en oberoende funktion någon annanstans i programmet. Observera också att du kan deklarera lokala variabler i ett block som du skulle i en funktion.

Standarden Objective-C-ramar använder detta designmönster för allt från sortering, till uppräkning, till animering. Faktum är att du även kan ersätta för-loop i det sista exemplet med NSArray's enumerateObjectsUsingBlock: metod, som visas här:

[sortedNumbers enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stopp) NSLog (@ "% lu:% 0.1f", idx, [obj floatValue]); om (idx == 2) // Stoppa uppräkning vid slutet av denna iteration. * Stopp = JA; ];

De obj parameter är det aktuella objektet, idx är det aktuella indexet och *sluta är ett sätt att avsluta uppräkningen tidigt. Ställa in *sluta pekaren till JA berättar metoden att sluta räkna upp efter den aktuella iterationen. Allt detta beteende specificeras av enumerateObjectsUsingBlock: metod.

Medan animering är lite off-topic för den här boken är det fortfarande värt en kort förklaring att hjälpa till att förstå användbarheten av block. UIView är en av de mest använda klasserna i IOS programmering. Det är en generisk grafisk behållare som låter dig animera innehållet via animateWithDuration: animationer: metod. Den andra parametern är ett block som definierar animationens slutliga tillstånd, och metoden visar automatiskt hur man animerar egenskaperna med den första parametern. Detta är ett elegant, användarvänligt sätt att definiera övergångar och annat timerbaserat beteende. Vi diskuterar animationer i mycket mer detalj i det kommande IOS Succinctly bok.


Lagring och exekvering av block

Bortsett från att de skickas till metoder kan block också lagras i variabler för senare användning. Detta användningsfall fungerar i huvudsak som ett alternativt sätt att definiera funktioner:

#importera  int 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);  returnera 0; 

Låt oss först kontrollera syntaxen för att deklarera blockvariabler: int (^ addIntegers) (int, int). Namnet på denna variabel är helt enkelt addIntegers (utan caret). Det kan vara förvirrande om du inte har använt block mycket länge. Det bidrar till att tänka på caret som blockens version av dereference-operatören (*). Till exempel a pekare kallad addIntegers skulle förklaras som * addIntegers-likaså a blockera med samma namn deklareras som ^ addIntegers. Men kom ihåg att detta bara är en ytlig likhet.

Förutom det variabla namnet måste du också deklarera alla metadata som är kopplade till blocket: antalet parametrar, deras typer och returtypen. Detta gör det möjligt för kompilatorn att tillämpa typsäkerhet med blockvariabler. Observera att caret är inte del av variabelnamnet-det krävs bara i deklarationen.

Därefter använder vi standardoperatören (=) för att lagra ett block i variabeln. Naturligtvis är blockets parametrar ((int x, int y)) måste matcha parametertyper som deklareras av variabeln ((int, int)). En semikolon krävs också efter blockdefinitionen, precis som en normal variabel uppgift. När den har befolts med ett värde kan variabeln kallas precis som en funktion: addIntegers (24, 18).

Parameter-Mindre Blockvariabler

Om ditt block inte tar några parametrar måste du uttryckligen deklarera detta i variabeln genom att placera ogiltig i parameterlistan:

void (^ contrived) (void) = ^ NSLog (@ "Detta är ett ganska konstruerat block."); NSLog (@ "Det matar bara ut dessa två meddelanden."); ; contrived ();

Arbeta med variabler

Variabler inuti blocken beter sig på samma sätt som de gör i normala funktioner. Du kan skapa lokala variabler i blocket, åtkomstparametrar som skickas till blocket och använda globala variabler och funktioner (t.ex.., NSLog ()). Men block har också tillgång till icke-lokala variabler, vilka är variabler från det omgivande lexiska räckviddet.

int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42

I detta fall, ursprungligt värde anses vara en icke-lokal variabel inom blocket eftersom den är definierad utanför blocket (inte lokalt, i förhållande till blocket). Naturligtvis innebär det faktum att icke-lokala variabler är skrivskyddade att du inte kan tilldela dem:

int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) initialValue = 5; // Detta kommer att kasta ett kompilatorfel. returnera initialValue + x; ;

Att ha tillgång till de omgivande (icke-lokala) variablerna är en stor sak när man använder inline-block som metodparametrar. Det ger ett bekvämt sätt att representera vilket tillstånd som krävs inom blocket.

Om du till exempel animerade färgen på en användargränssnittskomponent och målfärgen beräknades och lagrades i en lokal variabel före blockdefinitionen, kan du helt enkelt använda den lokala variabeln i blocket - inget extra arbete krävs. Om du inte hade tillgång till icke-lokala variabler skulle du ha passerat färgvärdet som en extra blockparameter. När din återuppringningsfunktion bygger på en stor del av det omgivande tillståndet kan detta vara mycket besvärligt.

Block är stängningar

Block har dock inte bara tillgång till icke-lokala variabler - de ser också till att de variablerna kommer att aldrig ändra, oavsett när eller var blocket exekveras. I de flesta programmeringsspråk kallas detta a stängning.

Stängningar fungerar genom att göra en konstant, skrivskyddad kopia av eventuella icke-lokala variabler och lagra dem i en referens tabellen med de uttalanden som utgör själva blocket. Dessa skrivskyddade värden används varje gång blocket exekveras, vilket betyder att även om den ursprungliga icke-lokala variabeln ändras, garanteras det värde som blocket använder som det var när blocket definierades.

Lagring av icke-lokala variabler i en referenstabell

Vi kan se detta i åtgärd genom att tilldela ett nytt värde till ursprungligt värde variabel från föregående exempel:

int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42 initialValue = 100; NSLog (@ "% i", addToInitialValue (10)); // Still 42.

Oavsett var du ringer addToInitialValue (), de ursprungligt värde som används av blockviljan alltid vara 32, för det var vad det var när det skapades. För alla ändamål är det som om ursprungligt värde variabel omvandlades till ett bokstavligt värde inuti blocket.

Så är användningen av block tvåfaldig:

  1. De låter dig representera funktionalitet som ett objekt.
  2. De låter dig representera statlig information utöver den funktionen.

Hela tanken bakom inkapslande funktionalitet i ett block är att kunna använda den senare i programmet. Stängningar gör det möjligt att säkerställa förutsägbart beteende närhelst ett block exekveras genom att frysa det omgivande tillståndet. Detta gör dem till en integrerad aspekt av blockprogrammering.

Mutable Block Variables

För de flesta fall är infångningstillstånd med stängningar intuitivt vad du kan förvänta dig från ett block. Det finns dock tider som kräver det motsatta beteendet. Möjliga blockvariabler är icke-lokala variabler som betecknas läs-skriv istället för standardläsningsenheten. För att göra en icke-lokal variabel mutabel måste du deklarera den med __blockera modifierare, som skapar en direkt länk mellan variabeln som används utanför blocket och den som används inuti blocket. Detta öppnar dörren för att använda block som iteratorer, generatorer och alla andra objekt som processer anger.

Skapa en direktlänk med en variabel blockvariabel

I följande exempel visas hur du gör en icke-lokal variabel mutabel:

#importera  #import "Person.h" int main (int argc, const char * argv []) @autoreleasepool __block NSString * namn = @ "Dave"; void (^ generateRandomName) (void) = ^ NSLog (@ "Ändra% @ till Frank", namn); namn = @ "frank" ; NSLog (@ "% @", namn); // Dave // ​​Ändra det från insidan av blocket. generateRandomName (); // Ändra Dave till Frank. NSLog (@ "% @", namn); // Frank // Ändra det från utsidan av blocket. namn = @ "Heywood"; generateRandomName (); // Byta Heywood till Frank.  returnera 0; 

Det ser nästan ut som det föregående exemplet, med två väsentliga skillnader. Först den icke-lokala namn variabel kan tilldelas inom blocket. För det andra ändrar variabeln utanför blocket gör uppdatera det värde som används i blocket. Det är även möjligt att skapa flera block som alla hanterar samma icke-lokala variabel.

Den enda försiktigheten att använda __blockera Modifierare är det kan inte användas på variabellängdslistor.

Definiera metoder som accepterar block

Förmodligen är det mer användbart att skapa metoder som accepterar block än att lagra dem i lokala variabler. Det ger dig möjlighet att lägga till din egen enumerateObjectsUsingBlock:-stil metoder till egna klasser.

Tänk på följande gränssnitt för Person klass:

// Person.h @ interface Person: NSObject @property int age; - (void) celebrateBirthdayWithBlock: (void (^) (int)) aktivitet; @slutet

De tomrummet (^) (int) kod är datatypen för det block du vill acceptera. I det här fallet accepterar vi ett block utan returvärde och ett enda heltalsparametrar. Observera att det, i motsats till blockvariabler, inte kräver ett namn på blocket - bara en obelagd ^ karaktär.

Du har nu alla nödvändiga färdigheter för att skapa metoder som accepterar block som parametrar. En enkel implementering för Person gränssnittet som visas i föregående exempel kan se ut som:

// Person.m #import "Person.h" @implementation Person @synthesize age = _age; - (void) celebrateBirthdayWithBlock: (void (^) (int)) aktivitet NSLog (@ "Det är en fest !!!"); aktivitet (self.age);  @slutet

Då kan du skicka en anpassningsbar aktivitet för att utföra på en Personfödelsedag så här:

// main.m int main (int argc, const char * argv []) @autoreleasepool Person * dave = [[Personallokering] init]; dave.age = 37; [dave celebrateBirthdayWithBlock: ^ (int ålder) NSLog (@ "Woot! Jag vänder% i", ålder + 1); ];  returnera 0; 

Det borde vara uppenbart att använda block som parametrar är oändligt mer flexibla än de standarddatatyper vi använt fram till detta kapitel. Du kan faktiskt berätta för en förekomst till do något, snarare än bara processdata.


Sammanfattning

Blocks låter dig representera uttalanden som Objective-C objekt, vilket gör det möjligt för dig att passera godtyckligt åtgärder till en funktion i stället för att vara begränsad till data. Detta är användbart för allt från iterating över en sekvens av objekt till animerande UI-komponenter. Block är en mångsidig förlängning till C-programmeringsspråket, och de är ett nödvändigt verktyg om du planerar att göra mycket arbete med standard iOS-ramar. I det här kapitlet lärde vi oss hur man skapar, lagrar och utför block, och vi lärde oss om förkroppsligar stängningar och __blockera lagringsmodifierare. Vi diskuterade också några vanliga användarparadigmer för block.

Således avslutar vår resa genom mål-C. Vi har täckt allt från grundläggande syntax till kärndatatyper, klasser, protokoll, egenskaper, metoder, minneshantering, felhantering och till och med avancerad användning av block. Vi fokuserade mer på språkfunktioner än att skapa grafiska applikationer, men det gav en solid grund för iOS-apputveckling. Nu hoppas jag att du känner dig väldigt bekväm med mål-C-språket.

Kom ihåg att Objective-C bygger på många av samma objektorienterade begrepp som andra OOP-språk. Medan vi bara berörde några objektorienterade designmönster i den här boken, är det praktiskt taget alla de organisatoriska paradigmer som finns tillgängliga för andra språk också möjliga i Objective-C. Det innebär att du enkelt kan utnyttja din befintliga objektorienterade kunskapsbas med verktygen som presenteras i de föregående kapitlen.


IOS Succinctly

Om du är redo att börja bygga funktionella iPhone- och iPad-applikationer, se till att kolla in den andra delen av serien, IOS Succinctly. Denna praktiska guide till apputveckling gäller alla Objektiv-C-färdigheter som förvärvats från den här boken till verkliga utvecklingssituationer. Vi går igenom alla de stora mål-C-ramarna och lär dig hur du utför uppgifter på vägen, bland annat: konfigurera användargränssnitt, fånga in, ritgrafik, spara och ladda filer och mycket, mycket mer.

Denna lektion representerar ett kapitel från Objective-C Succinctly, en gratis eBook från laget vid Syncfusion.