Udforskning af hurtige Initialisatorer / Objektpartnere

i denne artikel vil jeg udforske nogle af de gritty detaljer om hurtige initialisatorer. Hvorfor tage et så spændende emne op? Hovedsageligt fordi jeg lejlighedsvis har fundet mig forvirret af kompileringsfejl, når jeg forsøger at oprette en underklasse af Uivevisning, eller når jeg forsøger at oprette en hurtig version af en gammel Objective-C klasse, for eksempel. Så jeg besluttede at grave dybere for at få en bedre forståelse af nuancerne hos hurtige initialisatorer. Da min baggrund / erfaring for det meste har involveret at arbejde med Groovy, Java og Javascript, vil jeg sammenligne nogle af hurtige initialiseringsmekanismer med Java & Groovy. Jeg opfordrer dig til at fyre op en legeplads i kode og lege med kodeeksemplerne selv. Jeg opfordrer dig også til at læse Initialiseringsafsnittet i den hurtige 2.1 sprogguide, som er den primære kilde til information til denne artikel. Endelig vil mit fokus være på referencetyper (klasser) snarere end værdityper (strukturer).

Hvad Er En Initialisator?

en initialisator er en særlig type metode i en hurtig struktur, klasse eller enum, der er ansvarlig for at sikre, at en nyoprettet forekomst af struktur, klasse eller enum er fuldt initialiseret, før de er klar til brug. De spiller den samme rolle som en “konstruktør” i Java og Groovy. Hvis du er bekendt med Objective-C, skal du bemærke, at hurtige initialisatorer adskiller sig fra Objective-C initialisatorer, idet de ikke returnerer en værdi.

underklasser arver generelt ikke Initialisatorer

en af de første ting at huske på er, at “hurtige underklasser ikke arver deres superklasse initialisatorer som standard”, ifølge sprogguiden. (Vejledningen forklarer, at der er scenarier, hvor superklasse initialisatorer automatisk arves. Vi dækker disse ekstraordinære scenarier senere). Dette er i overensstemmelse med, hvordan Java (og i forlængelse heraf Groovy) fungerer. Overvej følgende:

ligesom med Java & Groovy, er det fornuftigt, at dette ikke er tilladt (selvom der som med de fleste ting kan være noget argument på dette punkt. Se denne Stackoverløbspost). Hvis det var tilladt, initialisering af” instrument ” ejendom musiker ville blive omgået, potentielt forlader din musiker instans i en ugyldig tilstand. Men med Groovy ville jeg normalt ikke gider med at skrive initialisatorer (dvs.konstruktører). Snarere vil jeg bare bruge kortkonstruktøren Groovy giver implicit, hvilket giver dig mulighed for frit at vælge og vælge hvilke egenskaber du vil indstille ved konstruktion. For eksempel er følgende perfekt gyldig Groovy-kode:

Bemærk, at du kan inkludere enhver ejendom, inklusive dem, der leveres af superklasser, men du behøver ikke at specificere alle (eller nogen) af dem, og de behøver ikke at være specificeret i en bestemt rækkefølge. Denne form for ultra-fleksibel initialisator leveres ikke af Hurtig. Den nærmeste ting i hurtig er de automatisk leverede medlemsinitialisatorer til strukturer. Men rækkefølgen af argumenterne i en medlems initialisator er signifikant, selvom de er navngivet, og afhænger af den rækkefølge, de er defineret i:
alligevel, tilbage til klasser – Groovy filosofi om post-construction objekt gyldighed er klart meget forskellig fra hurtig s. Dette er blot en af mange måder, hvorpå Groovy adskiller sig fra hurtig.

udpegede vs bekvemmelighed Initialisatorer

før vi kommer for langt ned i kaninhullet, bør vi afklare et vigtigt koncept: I hurtig, er en initialisator kategoriseret som værende enten en udpeget eller en bekvemmelighed initialisator. Jeg vil forsøge at forklare forskellen både konceptuelt og syntaktisk. Hver klasse skal have mindst en udpeget initialisator, men kan have flere (“udpeget” betyder ikke “enkelt”). En udpeget initialisator betragtes som en primær initialisator. De er head-honchos. De er i sidste ende ansvarlige for at sikre, at alle ejendomme initialiseres. På grund af dette ansvar kan det undertiden blive en smerte at bruge en udpeget initialisator hele tiden, da de muligvis kræver flere argumenter. Forestil dig at arbejde med en klasse, der har flere egenskaber, og du skal oprette flere forekomster af den klasse, der er næsten identiske bortset fra en eller to egenskaber. (For argumentets skyld, lad os også antage, at der ikke er nogen fornuftige standarder, der kunne have været tildelt egenskaber, når de erklæres). Lad os for eksempel sige, at vores Personklasse også havde spisermad og nydemusikegenskaber. Selvfølgelig, disse to ting ville blive sat til sandt det meste af tiden, men man ved aldrig. Tag et kig:

nu har vores Personklasse fire egenskaber, der skal indstilles, og vi har en udpeget initialisator, der kan gøre jobbet. Den udpegede initialisator er den første, den der tager 4 argumenter. Det meste af tiden vil de sidste to argumenter have værdien “Sand”. Det ville være en smerte at skulle fortsætte med at specificere dem, hver gang vi ønsker at skabe en typisk Person. Det er her de to sidste initialisatorer kommer ind, dem der er markeret med bekvemmelighedsmodifikatoren. Dette mønster skal se bekendt ud for en Java-udvikler. Hvis du har en konstruktør, der tager flere argumenter, end du virkelig har brug for at håndtere hele tiden, kan du skrive forenklede konstruktører, der tager en delmængde af disse argumenter og giver fornuftige standarder for de andre. Bekvemmelighedsinitialisatorerne skal delegere enten til en anden, måske mindre praktisk, bekvemmelighedsinitialisator eller til en udpeget initialisator. I sidste ende skal en udpeget initialisator involveres. Yderligere, hvis dette er en underklasse, skal den udpegede initialisator kalde en udpeget initialisator fra dens umiddelbare superklasse.

et eksempel fra den virkelige verden til brug af bekvemmelighedsmodifikatoren kommer fra uikits Uibesierpath-klasse. Jeg er sikker på, at du kan forestille dig, at der er flere måder at specificere en sti på. Som sådan giver Uibesierpath flere bekvemmelighedsinitialisatorer:

offentlig bekvemmelighed init(rect: CGRect)
offentlig bekvemmelighed init(ovalInRect rect: CGRect)
offentlig bekvemmelighed init(roundedRect rect: cgrect, cornerRadius: cgfloat)
offentlig bekvemmelighed init(roundedRect rect: CGRect, byoundingcorners corners: uirectcorner, Cornerradii: cgstørrelse)
offentlig bekvemmelighed init(arccenter Center: Cgpoint, radius: cgfloat, startAngle: CGFloat, fare: CGFloat, med uret: Bool)
offentlig bekvemmelighed init(CGPath: CGPath)

tidligere sagde jeg, at en klasse kan have flere udpegede initialisatorer. Så hvordan ser det ud? En vigtig forskel, der håndhæves af kompilatoren, mellem udpegede initialisatorer og bekvemmelighedsinitialisatorer er, at udpegede initialisatorer muligvis ikke delegerer til en anden initialisator i samme klasse (men skal delegere til en udpeget initialisator i dens umiddelbare superklasse). Se på den anden initialisator af Person, den der tager et enkelt argument med navnet “unDead”. Da denne initialisator ikke er markeret med bekvemmelighedsmodifikatoren, behandler Hurtig Den som en udpeget initialisator. Som sådan kan det ikke delegere til en anden initialisator personligt. Prøv at kommentere de første fire linjer og ikke kommentere den sidste linje. Compileren vil klage, og Kcode bør forsøge at hjælpe dig ud ved at foreslå, at du ordne det ved at tilføje bekvemmelighed modifier.

overvej nu musikerens underklasse af Person. Det har en enkelt initialisator, og det skal derfor være en udpeget initialisator. Som sådan, det skal kalde en udpeget initialisator af den umiddelbare superklasse, Person. Husk: mens udpegede initialisatorer ikke kan delegere til en anden initialisator af samme klasse, skal bekvemmelighedsinitialisatorer gøre det. En udpeget initialisator skal også kalde en udpeget initialisator af dens umiddelbare superklasse. Se sprogguiden for flere detaljer (og smuk grafik).

Initialiseringsfaser

som den hurtige sprogguide forklarer, er der to initialiseringsfaser. Faserne afgrænses af opkaldet til den superklasse, der er udpeget initialisator. Fase 1 er forud for opkaldet til superklassen udpeget initialisator, fase 2 er efter. En underklasse skal initialisere alle sine egne egenskaber i fase 1, og den indstiller muligvis ikke nogen egenskaber defineret af superklassen før fase 2.
her er en kodeeksempel, tilpasset fra en prøve, der findes i sprogguiden, der viser, at du skal initialisere de egne egenskaber for en underklasse, før du påberåber dig den superklasse, der er udpeget initialisator. Du får muligvis ikke adgang til egenskaber, der leveres af superklassen, før efter at du har påberåbt dig den superklasse, der er udpeget initialisator. Endelig kan du muligvis ikke ændre konstant lagrede egenskaber, efter at den superklasse-udpegede initialisator er blevet påberåbt.

tilsidesættelse af en Initialisator

nu hvor vi er overbeviste om, at underklasser generelt ikke arver initialisatorer, og vi er klar over betydningen af og sondringen mellem udpegede og bekvemmelighedsinitialisatorer, lad os overveje, hvad der sker, når du vil have en underklasse til at tilsidesætte en initialisator fra den umiddelbare superklasse. Der er fire scenarier, som jeg gerne vil dække, da der er to typer initialisator. Så lad os tage dem en efter en med enkle kodeeksempler for hvert tilfælde:

en udpeget initialisator, der matcher en superklasse udpeget initialisator
dette er et typisk scenario. Når du gør dette, skal du anvende override-modifikatoren. Bemærk, at dette scenario er i kraft, selv når du “tilsidesætter” en automatisk leveret standardinitialisator (dvs.når superklassen ikke definerer nogen eksplicit initialisator. I dette tilfælde giver hurtig en implicit. Java-udviklere skal være bekendt med denne adfærd). Denne automatisk leverede standardinitialisator er altid en udpeget initialisator.
en udpeget initialisator, der matcher en superklasse-bekvemmelighedsinitialisator
lad os nu antage, at du vil tilføje en udpeget initialisator til din underklasse, der tilfældigvis matcher en bekvemmelighedsinitialisator i forælderen. I henhold til reglerne for initialiseringsdelegering, der er beskrevet i sprogguiden, skal din underklasse udpegede initialiserer delegere op til en udpeget initialiserer af den umiddelbare superklasse. Det vil sige, Du må ikke delegere op til forældrenes matchende bekvemmelighedsinitialisator. Af hensyn til argumentet, Antag også, at underklasse ikke kvalificerer sig til at arve superklasse initialisatorer. Derefter, da du aldrig kunne oprette en forekomst af din underklasse ved direkte at påberåbe sig superklassens bekvemmelighedsinitialisator, den matchende bekvemmelighedsinitialisator er ikke, og kunne aldrig være, involveret i initialiseringsprocessen alligevel. Derfor tilsidesætter du det ikke rigtig, og tilsidesættelsesmodifikatoren gælder ikke.

en bekvemmelighedsinitialisator, der matcher en superklasse udpeget initialisator
i dette scenario skal du forestille dig, at du har en underklasse, der tilføjer sine egne egenskaber, hvis standardværdi kan (men ikke behøver at være) beregnes ud fra de værdier, der er tildelt en eller flere overordnede klasseegenskaber. Antag, at du også kun vil have en udpeget initialisator til din underklasse. Du kan tilføje en bekvemmelighedsinitialisator til din underklasse, hvis signatur svarer til en udpeget initialisator af forældreklassen. I dette tilfælde vil din nye initialisator have brug for både bekvemmelighed og Tilsidesæt modifikatorer. Her er en gyldig kodeeksempel til at illustrere denne sag:
en bekvemmelighedsinitialisator, der matcher en superklasse bekvemmelighedsinitialisator
hvis du vil tilføje en bekvemmelighedsinitialisator til din underklasse, der tilfældigvis matcher signaturen til en bekvemmelighedsinitialisator af din superklasse, skal du bare gå lige foran. Som jeg forklarede ovenfor, kan du alligevel ikke tilsidesætte bekvemmelighedsinitialisatorer. Så du vil inkludere bekvemmelighedsmodifikatoren, men udelad tilsidesættelsesmodifikatoren og behandle den ligesom enhver anden bekvemmelighedsinitialisator.

en nøgleudtagning fra dette afsnit er, at override modifier kun bruges og skal bruges, hvis du tilsidesætter en superklasse udpeget initialisator. (Mindre afklaring at gøre her: hvis du tilsidesætter en påkrævet initialisator, vil du bruge den krævede modifikator i stedet for override modifikatoren. Den krævede modifikator indebærer tilsidesætningsmodifikatoren. Se afsnittet nødvendige Initialisatorer nedenfor).

når Initialisatorer arves

nu til de førnævnte scenarier, hvor superklasse initialisatorer arves. Som den hurtige sprogguide forklarer, Hvis din underklasse giver standardværdier for alle sine egne egenskaber ved erklæring, og definerer ikke nogen af sine egne udpegede initialisatorer, så arver den automatisk alle sine superklasse udpegede initialisatorer. Eller hvis din underklasse giver en implementering af alle de superklasse udpegede initialisatorer, arver den automatisk alle superklasse-bekvemmelighedsinitialisatorer. Dette er i overensstemmelse med reglen i hurtig, at initialisering af klasser (og strukturer) ikke må efterlade lagrede egenskaber i en ubestemt tilstand.

jeg snuble over nogle “interessante” adfærd, mens jeg eksperimenterede med bekvemmelighedsinitialisatorer, udpegede initialisatorer og arvereglerne. Jeg fandt ud af, at det er muligt at opsætte en ond cirkel utilsigtet. Overvej følgende eksempel:

RecipeIngredient-klassen tilsidesætter alle de udpegede fødevareklasser initialisatorer, og Derfor arver den automatisk alle superklassens bekvemmelighedsinitialisatorer. Men initialisatoren til madvenlighed delegerer med rimelighed til sin egen udpegede initialisator, som er blevet tilsidesat af underklassen RecipeIngredient. Så det er ikke Madversionen af den init(navn: streng) initialisator, der påberåbes, men den tilsidesatte version i RecipeIngredient. Den tilsidesatte version drager fordel af det faktum, at underklassen har arvet madens bekvemmelighedsinitiator, og der er den – du har en cyklus. Jeg ved ikke, om dette ville blive betragtet som en programmørfejl eller en kompilatorfejl (jeg rapporterede det som en fejl: https://bugs.swift.org/browse/SR-512 ). Forestil dig, at mad er en klasse fra en 3.part, og at du ikke har adgang til kildekoden, så du ikke ved, hvordan den faktisk implementeres. Du ville ikke vide (indtil runtime) at bruge det på den måde, der er vist i dette eksempel, ville få dig fanget i en cyklus. Så jeg tror, det ville være bedre, hvis kompilatoren hjalp os herude.

fejlbare Initialisatorer

Forestil dig, at du har designet en klasse, der har visse invarianter, og du vil gerne håndhæve disse invarianter fra det øjeblik, en forekomst af klassen oprettes. For eksempel modellerer du måske en faktura, og du vil sikre dig, at beløbet altid er ikke-negativt. Hvis du tilføjede en initialisator, der tager et beløbsargument af typen Dobbelt, hvordan kunne du sikre dig, at du ikke overtræder din invariant? En strategi er blot at kontrollere, om argumentet er ikke-negativt. Hvis det er, brug det. Ellers standard til 0. For eksempel:

dette ville fungere og kan være acceptabelt, hvis du dokumenterer, hvad din initialisator gør (især hvis du planlægger at gøre din klasse tilgængelig for andre udviklere). Men du har måske svært ved at forsvare denne strategi, da det slags fejer problemet under tæppet. En anden tilgang, der understøttes af Hurtig, ville være at lade initialisering mislykkes. Det vil sige, du ville gøre din initialisator fejlbarlig.

som denne artikel beskriver, blev fejlbare initialisatorer tilføjet til hurtig som en måde at eliminere eller reducere behovet for fabriksmetoder, “som tidligere var den eneste måde at rapportere fejl på” under objektkonstruktion. For at gøre en initialisator fejlbarlig, du blot tilføje ? eller ! tegn efter init-nøgleordet (dvs. init? eller init! ). Derefter, efter at alle egenskaber er indstillet, og alle de andre regler vedrørende delegering er opfyldt, vil du tilføje noget logik for at kontrollere, at argumenterne er gyldige. Hvis de ikke er gyldige, udløser du en initialiseringsfejl med return nul. Bemærk, at dette ikke betyder, at initialisatoren nogensinde returnerer noget. Sådan kan vores Fakturaklasse se ud med en fejlbar initialisator:
Bemærk noget andet om, hvordan vi bruger resultatet af objektoprettelsen? Det er som om vi behandler det som en valgfri, ikke? Nå, det er præcis, hvad vi laver! Når vi bruger en fejlbar initialisator, får vi enten nul (hvis en initialiseringsfejl blev udløst) eller en valgfri(faktura). Det vil sige, hvis initialiseringen var vellykket, ender vi med en valgfri, der ombryder den Fakturainstans, vi er interesseret i, så vi er nødt til at pakke den ud. (Som en side, Bemærk at Java også har Optionals fra Java 8).

fejlbare initialisatorer er ligesom de andre typer initialisatorer, vi har diskuteret med hensyn til tilsidesættelse og delegering, udpeget vs bekvemmelighed osv…. Faktisk kan du endda tilsidesætte en fejlbar initialisator med en ikke-fejlbar initialisator. Du kan dog ikke tilsidesætte en ikke-fejlbarlig initialisator med en fejlbar.

du har måske bemærket fejlbare initialisatorer fra at håndtere Uivevisning eller Uivecontroller, som begge giver en fejlbar initialisator init?(koder aDecoder: NSCoder). Denne initialisator kaldes, når din visning eller Visningskontroller er indlæst fra en spids. Det er vigtigt at forstå, hvordan fejlbare initialisatorer fungerer. Jeg anbefaler kraftigt, at du læser afsnittet om mislykkede Initialisatorer i den hurtige sprogguide for en grundig forklaring.

påkrævede Initialisatorer

den påkrævede modifikator bruges til at angive, at alle underklasser skal implementere den berørte initialisator. På forsiden af det lyder det ret simpelt og ligetil. Det kan til tider blive lidt forvirrende, hvis du ikke forstår, hvordan reglerne om initialiseringsarv, der er diskuteret ovenfor, spiller ind. Hvis en underklasse opfylder kriterierne, hvormed superklasse-initialisatorer arves, inkluderer sættet af arvelige initialisatorer de markerede krævede. Derfor opfylder underklassen implicit den kontrakt, der er pålagt af den krævede modifikator. Det implementerer de nødvendige initialisatorer, du kan bare ikke se det i kildekoden.

hvis en underklasse giver en eksplicit (dvs.ikke arvet) implementering af en påkrævet initialisator, tilsidesætter den også superklasse-implementeringen. Den krævede modifikator indebærer tilsidesættelse, så tilsidesætningsmodifikatoren bruges ikke. Du kan medtage det, hvis du vil, men det ville være overflødigt, og kode vil narre dig om det.

den hurtige sprogguide siger ikke meget om den krævede modifikator, så jeg forberedte en kodeeksempel (se nedenfor) med kommentarer for at forklare dens formål og beskrive, hvordan det fungerer. For mere information, se denne artikel af Anthony Levings.

Special Case: udvidelse af Uivevisning

en af de ting, der fik mig til at grave dybt ned i hurtige initialisatorer, var mit forsøg på at finde ud af en måde at oprette et sæt udpegede initialisatorer uden at duplikere initialiseringslogik. For eksempel arbejdede jeg gennem denne Uivevisningstutorial af Ray Venderlich og konverterede sin Objective-C-kode til hurtig, da jeg gik (du kan se på min hurtige version her). Hvis du ser på den tutorial, vil du se, at hans Ratevisningsunderklasse af Uivvisning har en baseInit-metode, som begge de udpegede initialisatorer bruger til at udføre almindelige initialiseringsopgaver. Det virker som en god tilgang til mig – du vil ikke duplikere de ting i hver af disse initialisatorer. Jeg ønskede at genskabe den teknik i min hurtige version af Ratevisning. Men jeg fandt det vanskeligt, fordi en udpeget initialisator ikke kan delegere til en anden initialisator i samme klasse og ikke kan kalde metoder i sin egen klasse, før den delegerer til superklassens initialisator. På det tidspunkt er det for sent at indstille konstante egenskaber. Selvfølgelig kan du omgå denne begrænsning ved ikke at bruge konstanter, men det er ikke en god løsning. Så jeg regnede med, at det var bedst at bare angive standardværdier for de lagrede egenskaber, hvor de er erklæret. Det er stadig den bedste løsning, som jeg i øjeblikket kender til. Jeg fandt dog ud af en alternativ teknik, der bruger initialisatorer.

se på følgende eksempel. Dette er et uddrag fra Ratevise med Convenienceinits.hurtig, som er en alternativ version af min hurtige port af Ratevisning. At være en underklasse af Uivevisning, der ikke giver standardværdier for alle sine egne lagrede egenskaber ved erklæring, skal denne alternative version af Ratevisning i det mindste give en eksplicit implementering af Uivevisningens krævede init?(koder aDecoder: NSCoder) initialisator. Vi vil også gerne give en eksplicit implementering af Uivevisets init (ramme: Cgrect) initialiserer for at sikre, at initialiseringsprocessen er konsistent. Vi ønsker, at vores lagrede egenskaber skal konfigureres på samme måde, uanset hvilken initialisator der bruges.
Bemærk, at jeg tilføjede bekvemmelighedsmodifikatoren til de tilsidesatte versioner af uivs initialisatorer. Jeg tilføjede også en fejlbarlig udpeget initialisator til underklassen, som begge de tilsidesatte (bekvemmelighed) initialisatorer delegerer til. Denne enkelt udpegede initialisator tager sig af at oprette alle de lagrede egenskaber (inklusive konstanter – jeg behøvede ikke at ty til at bruge var til alt). Det virker, men jeg synes det er temmelig kludgy. Jeg foretrækker bare at angive standardværdier for mine gemte egenskaber, hvor de er erklæret, men det er godt at vide, at denne mulighed findes, hvis det er nødvendigt.

You might also like

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.