Blocks zijn eigenlijk een uitbreiding op de programmeertaal C, maar ze worden zwaar gebruikt door de Objective-C-raamwerken van Apple. Ze lijken op de lambda's van C #, omdat ze je in staat stellen een regel met inline-regels te definiëren en deze door te geven aan andere functies alsof het een object is.
Gegevens verwerken met functies versus willekeurige acties uitvoeren met blokkenBlokken zijn ongelooflijk handig voor het definiëren van callback-methoden, omdat ze u in staat stellen de gewenste functionaliteit op het punt van aanroep te definiëren in plaats van ergens anders in uw programma. Bovendien zijn blokken geïmplementeerd als sluitingen (net als lambda's in C #), wat het mogelijk maakt om de lokale staat rondom het blok vast te leggen zonder extra werk.
Bloksyntaxis kan een beetje verontrustend zijn in vergelijking met de Objective-C-syntaxis die we in dit boek hebben gebruikt, dus maak je geen zorgen als het een tijdje duurt om je er comfortabel bij te voelen. We zullen beginnen met een eenvoudig voorbeeld te bekijken:
^ (int x) keer terug x * 2; ;
Dit definieert een blok dat een integer-parameter vereist, X
, en retourneert die waarde vermenigvuldigd met twee. Afgezien van de caret (^
), dit lijkt op een normale functie: het heeft een parameterlijst tussen haakjes, een instructieblok ingesloten tussen accolades en een (optionele) retourwaarde. In C # is dit geschreven als:
x => x * 2;
Maar blokken zijn niet beperkt tot eenvoudige uitdrukkingen - ze kunnen een willekeurig aantal uitspraken bevatten, net als een functie. U kunt bijvoorbeeld een toevoegen NSLog ()
bellen voordat een waarde wordt geretourneerd:
^ (int x) NSLog (@ "Ongeveer om% i te vermenigvuldigen met 2.", x); retourneer x * 2; ;
Als uw blok geen parameters heeft, kunt u de parameterlijst helemaal weglaten:
^ NSLog (@ "Dit is een behoorlijk gekunsteld blok."); NSLog (@ "Het voert deze twee berichten gewoon uit."); ;
Op zichzelf is een blok niet zo nuttig. Doorgaans geeft u deze door aan een andere methode als callback-functie. Dit is een zeer krachtige taalfunctie, waarmee u kunt trakteren functionaliteit als een parameter, in plaats van beperkt te zijn tot gegevens. Je kunt een blok aan een methode doorgeven zoals elke andere letterlijke waarde:
[anObject doSomethingWithBlock: ^ (int x) NSLog (@ "% i wordt vermenigvuldigd met twee"); retourneer x * 2; ];
De doSomethingWithBlock:
implementatie kan het blok rennen net zoals het een functie zou hebben, die de deur opent naar veel nieuwe organisatorische paradigma's.
Als een meer praktisch voorbeeld, laten we een kijkje nemen naar de sortUsingComparator:
methode gedefinieerd door NSMutableArray
. Dit biedt exact dezelfde functionaliteit als de sortedArrayUsingFunction:
methode die we in de Datatypes hoofdstuk, behalve dat u het sorteeralgoritme in een blok definieert in plaats van een volwaardige functie:
Inclusief codevoorbeeld: SortUsingBlock
#importerenint main (int argc, const char * argv []) @autoreleasepool NSMutableArray * numbers = [NSMutableArray arrayWithObjects: [NSNumber numberWithFloat: 3.0f], [NSNumber numberWithFloat: 5.5f], [NSNumber numberWithFloat: 1.0f], [ NSNumber numberWithFloat: 12.2f], nihil]; [numbers sortUsingComparator: ^ NSComparisonResult (id obj1, id obj2) float number1 = [obj1 floatValue]; float number2 = [obj2 floatValue]; als (nummer1 < number2) return NSOrderedAscending; else if (number1 > number2) return NSOrderedDescending; else retourneer NSOrderedSame; ]; voor (int i = 0; i<[numbers count]; i++) NSLog(@"%i: %0.1f", i, [[numbers objectAtIndex:i] floatValue]); return 0;
Nogmaals, dit is een eenvoudige oplopende sortering, maar de mogelijkheid om het sorteeralgoritme op dezelfde plaats als de functie-aanroep te definiëren, is intuïtiever dan een andere functie elders in het programma te moeten definiëren. Merk ook op dat u lokale variabelen in een blok kunt declareren, net als in een functie.
De standaard Objective-C-kaders gebruiken dit ontwerppatroon voor alles, van sorteren, opsommen, tot animatie. In feite zou je zelfs de for-loop in het laatste voorbeeld kunnen vervangen door NSArray
's enumerateObjectsUsingBlock:
methode, zoals hier getoond:
[selectedNumbers enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) NSLog (@ "% lu:% 0.1f", idx, [obj floatValue]); if (idx == 2) // Stop met opsommen aan het einde van deze iteratie. * stop = JA; ];
De obj
parameter is het huidige object, idx
is de huidige index, en *hou op
is een manier om de opsomming voortijdig te verlaten. Het instellen van *hou op
aanwijzer naar JA
vertelt de methode om te stoppen met opsommen na de huidige iteratie. Al dit gedrag wordt gespecificeerd door de enumerateObjectsUsingBlock:
methode.
Hoewel animatie een beetje afwijkend is voor dit boek, is het toch een korte uitleg waard om het nut van blokken te helpen begrijpen. UIView
is een van de meest gebruikte klassen in iOS-programmering. Het is een generieke grafische container waarmee u de inhoud kunt animeren via de animateWithDuration: animaties:
methode. De tweede parameter is een blok dat de uiteindelijke status van de animatie definieert, en de methode berekent automatisch hoe de eigenschappen met behulp van de eerste parameter te animeren. Dit is een elegante, gebruiksvriendelijke manier om overgangen en ander op timer gebaseerd gedrag te definiëren. We zullen animaties in meer detail bespreken in de aankomende iOS bondig boek.
Afgezien van het doorgeven ervan aan methoden, kunnen blokken ook worden opgeslagen in variabelen voor later gebruik. Deze use case dient voornamelijk als een alternatieve manier om functies te definiëren:
#importerenint main (int argc, const char * argv []) @autoreleasepool int (^ addIntegers) (int, int); addIntegers = ^ (int x, int y) return x + y; ; int resultaat = addIntegers (24, 18); NSLog (@ "% i", resultaat); retourneer 0;
Laten we eerst de syntaxis bekijken voor het declareren van blokvariabelen: int (^ addIntegers) (int, int)
. De naam van deze variabele is eenvoudig addIntegers
(zonder de caret). Dit kan verwarrend zijn als je blokken niet lang gebruikt hebt. Het helpt de caret te zien als de blokversie van de dereference-operator (*
). Bijvoorbeeld a wijzer riep addIntegers
zou worden verklaard als * addIntegers
-evenzo, a blok van dezelfde naam wordt aangegeven als ^ addIntegers
. Houd er echter rekening mee dat dit slechts een oppervlakkige overeenkomst is.
Naast de naam van de variabele moet u ook alle metagegevens die aan het blok zijn gekoppeld declareren: het aantal parameters, hun typen en het retourneringstype. Dit stelt de compiler in staat om typeveiligheid met blokvariabelen af te dwingen. Merk op dat het caret is niet deel van de variabele naam-het is alleen vereist in de verklaring.
Vervolgens gebruiken we de standaard toewijzingsoperator (=
) om een blok in de variabele op te slaan. Natuurlijk, de parameters van het blok ((int x, int y)
) moet overeenkomen met parametertypen die door de variabele zijn gedeclareerd ((int, int)
). Een puntkomma is ook vereist na de blokdefinitie, net als een normale variabele toewijzing. Als het eenmaal is gevuld met een waarde, kan de variabele net als een functie worden aangeroepen: addIntegers (24, 18)
.
Als je blok geen parameters opneemt, moet je dit expliciet aangeven in de variabele door te plaatsen leegte
in de parameterlijst:
void (^ geconstrueerd) (void) = ^ NSLog (@ "Dit is een behoorlijk gekunsteld blok."); NSLog (@ "Het voert deze twee berichten gewoon uit."); ; gekunstelde ();
Variabelen in blokken gedragen zich op dezelfde manier als in normale functies. U kunt lokale variabelen binnen het blok maken, toegangsparameters doorgeven aan het blok en algemene variabelen en functies gebruiken (bijv., NSLog ()
). Maar blokken hebben ook toegang tot niet-lokale variabelen, die variabelen zijn van de omsluitende lexicale scope.
int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42
In dit geval, beginwaarde
wordt beschouwd als een niet-lokale variabele binnen het blok omdat het buiten het blok is gedefinieerd (niet lokaal, ten opzichte van het blok). Natuurlijk houdt het feit dat niet-lokale variabelen alleen-lezen zijn in dat u ze niet kunt toewijzen:
int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) initialValue = 5; // Dit levert een compileerfout op. return initialValue + x; ;
Toegang hebben tot de omringende (niet-lokale) variabelen is een groot probleem bij het gebruik van inline-blokken als methodeparameters. Het biedt een handige manier om elke gewenste status binnen het blok weer te geven.
Als u bijvoorbeeld de kleur van een UI-component animeerde en de doelkleur werd berekend en opgeslagen in een lokale variabele vóór de blokdefinitie, kon u eenvoudig de lokale variabele binnen het blok gebruiken - geen extra werk vereist. Als u geen toegang had tot niet-lokale variabelen, zou u de kleurwaarde hebben doorgegeven als een extra blokparameter. Wanneer uw callback-functionaliteit afhankelijk is van een groot deel van de omliggende staat, kan dit erg omslachtig zijn.
Blokken hebben echter niet alleen toegang aan niet-lokale variabelen - ze zorgen er ook voor dat die variabelen dat wel doen nooit veranderen, ongeacht waar of wanneer het blok wordt uitgevoerd. In de meeste programmeertalen wordt dit a genoemd sluiting.
Sluitingen werken door een constante, alleen-lezen kopie te maken van niet-lokale variabelen en deze op te slaan in a referentietabel met de uitspraken die het blok zelf vormen. Deze alleen-lezen waarden worden gebruikt elke keer dat het blok wordt uitgevoerd, wat betekent dat zelfs als de oorspronkelijke niet-lokale variabele verandert, de waarde die door het blok wordt gebruikt, gegarandeerd hetzelfde is als toen het blok werd gedefinieerd.
Niet-lokale variabelen opslaan in een referentietabelWe kunnen dit in actie zien door een nieuwe waarde toe te kennen aan de beginwaarde
variabele van het vorige voorbeeld:
int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42 initialValue = 100; NSLog (@ "% i", addToInitialValue (10)); // Nog steeds 42.
Waar je ook belt addToInitialValue ()
, de beginwaarde
gebruikt door het blok zal altijd worden 32
, omdat dat het was toen het werd gemaakt. In alle opzichten is het alsof het beginwaarde
variabele werd omgezet in een letterlijke waarde binnen in het blok.
Het nut van blokken is dus tweevoudig:
Het hele idee achter het inkapselen van functionaliteit in een blok is om het te kunnen gebruiken later in het programma. Afsluitingen zorgen voor voorspelbaar gedrag telkens als een blok wordt uitgevoerd door de omringende staat te bevriezen. Dit maakt ze een integraal onderdeel van blokprogrammering.
In de meeste gevallen is het vastleggen van de status met sluitingen intuïtief wat u van een blok zou verwachten. Er zijn echter tijden die pleiten voor het tegenovergestelde gedrag. Veranderbare blokvariabelen zijn niet-lokale variabelen die worden aangeduid als lezen / schrijven in plaats van als standaard alleen-lezen. Als u een niet-lokale variabele wijzigbaar wilt maken, moet u deze declareren bij de __blok
modifier, waarmee een directe link wordt gemaakt tussen de variabele die buiten het blok wordt gebruikt en de variabele die in het blok wordt gebruikt. Dit opent de deur naar het gebruik van blokken als iterators, generators en elk ander object dat de status verwerkt.
In het volgende voorbeeld ziet u hoe u een niet-lokale variabele wijzigbaar maakt:
#importeren#import "Person.h" int main (int argc, const char * argv []) @autoreleasepool __block NSString * name = @ "Dave"; void (^ generateRandomName) (void) = ^ NSLog (@ "Changing% @ to Frank", name); name = @ "Frank"; ; NSLog (@ "% @", naam); // Dave // Verander het van binnenuit het blok. generateRandomName (); // Dave veranderen in Frank. NSLog (@ "% @", naam); // Frank // Wijzig deze van buiten het blok. name = @ "Heywood"; generateRandomName (); // Veranderende Heywood naar Frank. retourneer 0;
Dit ziet er bijna precies hetzelfde uit als in het vorige voorbeeld, met twee zeer significante verschillen. Ten eerste, de niet-lokale naam
veranderlijk kan toegewezen vanuit het blok. Ten tweede, het veranderen van de variabele buiten het blok doet werk de waarde bij die in het blok wordt gebruikt. Het is zelfs mogelijk om meerdere blokken te maken die allemaal dezelfde niet-lokale variabele manipuleren.
Het enige voorbehoud bij het gebruik van de __blok
modifier is dat het kan niet worden gebruikt op arrays met een variabele lengte.
Het is aantoonbaar beter om methoden te maken die blokken accepteren, dan ze op te slaan in lokale variabelen. Het geeft je de mogelijkheid om je eigen toe te voegen enumerateObjectsUsingBlock:
-stijlmethoden voor aangepaste klassen.
Overweeg de volgende interface voor de Persoon
klasse:
// Person.h @interface Persoon: NSObject @property int age; - (void) celebrateBirthdayWithBlock: (void (^) (int)) activiteit; @einde
De void (^) (int)
code is het gegevenstype voor het blok dat u wilt accepteren. In dit geval accepteren we een blok zonder retourwaarde en één integerparameter. Merk op dat dit, in tegenstelling tot blokvariabelen, geen naam voor het blok vereist - alleen een onopgesmukt ^
karakter.
Je hebt nu alle benodigde vaardigheden om methoden te maken die blokken accepteren als parameters. Een eenvoudige implementatie voor de Persoon
interface getoond in het vorige voorbeeld kan er ongeveer zo uitzien:
// Person.m #import "Person.h" @implementation Persoon @synthetize age = _age; - (void) celebrateBirthdayWithBlock: (void (^) (int)) activity NSLog (@ "It's a party !!!"); activiteit (self.age); @end
Vervolgens kunt u een aanpasbare activiteit doorgeven om uit te voeren op a Persoon
is jarig zoals zo:
// main.m int main (int argc, const char * argv []) @autoreleasepool init []] (Persoon * dave = [[Person alloc] init]; dave.age = 37; [dave celebrateBirthdayWithBlock: ^ (int age) NSLog (@ "Woot! Ik verander% i", leeftijd + 1); ]; retourneer 0;
Het moet snel duidelijk zijn dat het gebruik van blokken als parameters oneindig flexibeler is dan de standaard gegevenstypes die we tot dit hoofdstuk hebben gebruikt. Je kunt een instantie echt vertellen do iets, in plaats van alleen gegevens te verwerken.
Met blokken kun je statements weergeven als Objective-C-objecten, waardoor je willekeurig kunt passeren acties naar een functie in plaats van beperkt te zijn gegevens. Dit is handig voor alles, van het itereren over een reeks objecten tot het animeren van UI-componenten. Blokken zijn een veelzijdige uitbreiding op de C-programmeertaal en ze zijn een noodzakelijk hulpmiddel als u van plan bent veel werk te doen met de standaard iOS-frameworks. In dit hoofdstuk hebben we geleerd blokken te maken, op te slaan en uit te voeren, en we leerden over de fijne kneepjes van sluitingen en de __blok
opslag modifier. We hebben ook enkele gangbare gebruiksparadigma's voor blokken besproken.
Aldus eindigt onze reis door Objective-C. We hebben alles behandeld, van basissyntaxis tot kerndatatypen, klassen, protocollen, eigenschappen, methoden, geheugenbeheer, foutafhandeling en zelfs het geavanceerde gebruik van blokken. We hebben ons meer gericht op taalfuncties dan het maken van grafische toepassingen, maar dit vormde een solide basis voor de ontwikkeling van iOS-apps. Ik hoop dat je je nu heel comfortabel voelt met de taal Objective-C.
Onthoud dat Objective-C afhankelijk is van veel van dezelfde objectgeoriënteerde concepten als andere OOP-talen. Hoewel we in dit boek slechts enkele object-georiënteerde ontwerppatronen hebben aangeroerd, zijn vrijwel alle organisatieparadigma's die beschikbaar zijn voor andere talen ook mogelijk in Objective-C. Dit betekent dat u uw bestaande objectgeoriënteerde kennisbank eenvoudig kunt gebruiken met de hulpmiddelen die in de voorgaande hoofdstukken zijn gepresenteerd.
Als je klaar bent om functionele iPhone- en iPad-applicaties te bouwen, kijk dan eens naar het tweede deel van deze serie, iOS bondig. Deze praktische handleiding voor app-ontwikkeling past alle Objective-C-vaardigheden die zijn verkregen uit dit boek toe op praktijksituaties in de echte wereld. We doorlopen alle belangrijke Objective-C-raamwerken en leren hoe je onderweg taken kunt uitvoeren, waaronder: het configureren van gebruikersinterfaces, het vastleggen van invoer, het tekenen van afbeeldingen, het opslaan en laden van bestanden, en nog veel, veel meer.
Deze les vertegenwoordigt een hoofdstuk uit Objective-C bondig, een gratis eBoek van het team van Syncfusion.