Articles

String literals i hurtig

at være i stand til at udtrykke grundlæggende værdier, såsom strenge og heltal, ved hjælp af inline literals er en væsentlig funktion i de fleste programmeringssprog. Imidlertid, mens mange andre sprog har støtte til specifikke bogstaver bagt i deres kompilator, Hurtig tager en meget mere dynamisk tilgang-ved hjælp af sit eget typesystem til at definere, hvordan forskellige bogstaver skal håndteres, gennem protokoller.

denne uge, lad os fokusere på strenglitteraler især ved at tage et kig på de mange forskellige måder, de kan bruges på, og hvordan vi — gennem hurtig ‘ s meget protokolorienterede design-er i stand til at tilpasse den måde, bogstaver fortolkes på, hvilket lader os gøre nogle virkelig interessante ting.

væsentlig Udvikler

væsentlig Udvikler: Deltag i et gratis online crashkursus for iOS-udviklere, der ønsker at blive seniorudviklere med sort bælte-det er: opnå et ekspertniveau af praktiske færdigheder og blive en del af de højest betalte udviklere i verden.

det grundlæggende

ligesom på mange andre sprog udtrykkes hurtige strenge gennem bogstaver omgivet af anførselstegn-og kan indeholde både specielle sekvenser — såsom nye linjer), undslapte tegn og interpolerede værdier:

let string = "\(user.name) says \"Hi!\"\nWould you like to reply?"// John says "Hi!"// Would you like to reply?

mens de funktioner, der er brugt ovenfor, allerede giver os en masse fleksibilitet og sandsynligvis er nok til langt de fleste brugssager, er der situationer, hvor mere kraftfulde måder at udtrykke bogstaver kan komme til nytte. Lad os se på nogle af dem, begyndende med når vi skal definere en streng, der indeholder flere tekstlinjer.

Multiline literals

selvom enhver standardstreng bogstavelig kan opdeles i flere linjer ved hjælp af\n, er det ikke altid praktisk — især hvis vi søger at definere et større stykke tekst som en inline bogstavelig.

heldigvis, siden Hurtig 4, er vi også i stand til at definere flerlinjestrengslitteraler ved hjælp af tre anførselstegn i stedet for kun en. For eksempel bruger vi denne mulighed til at udsende en hjælpetekst til et hurtigt script, hvis brugeren ikke overførte nogen argumenter, når han påberåbte det på kommandolinjen:

// We're comparing against 1 here, since the first argument passed// to any command line tool is the current path of execution.guard CommandLine.arguments.count > 1 else { print(""" To use this script, pass the following: - A string to process - The maximum length of the returned string """) // Exit the program with a non-zero code to indicate failure. exit(1)}

ovenfor bruger vi det faktum, at flerlinjestrengslitteraler bevarer deres tekstindrykning i forhold til det afsluttende sæt anførselstegn i bunden. De giver os også mulighed for meget mere frit at bruge uomskårne anførselstegn i dem, da de er defineret af et sæt på tre anførselstegn, hvilket gør grænserne for det bogstavelige meget mindre tilbøjelige til at blive tvetydige.

begge ovenstående to egenskaber gør multiline — bogstaver til et godt værktøj til at definere inline HTML — for eksempel i en eller anden form for værktøj til generering af hjemmesider, eller når du gengiver dele af en apps indhold ved hjælp af internetvisninger-som denne:

extension Article { var html: String { // If we want to break a multiline literal into separate // lines without causing an *actual* line break, then we // can add a trailing '\' to one of our lines. let twitterLink = """ <a href="https://twitter.com/\(author.twitterHandle)">\ @\(author.twitterHandle)</a> """ return """ <article> <h1>\(title)</h1> <div class="author"> <p>\(author.name)</p> \(twitterLink) </div> <div class="body">\(body)</div> </article> """ }}

ovenstående teknik kan også være virkelig nyttig, når du definerer strengbaserede testdata. Lad os f.eks. sige, at vores apps indstillinger skal kunne eksporteres, og at vi ønsker at skrive en test, der verificerer denne funktionalitet.

class SettingsTests: XCTestCase { func testXMLConversion() { let settings = Settings( messageLimit: 7, enableSync: true, signature: "Sent from my Swift app" ) XCTAssertEqual(settings.xml, """ <?xml version="1.0" encoding="UTF-8"?> <settings> <messagelimit>7</messagelimit> <enablesync>1</enablesync> <signature>Sent from my Swift app</signature> </settings> """) }}

fordelen ved at definere testdata inline, som vi gør ovenfor, er, at det bliver meget lettere hurtigt at få øje på eventuelle fejl, der er foretaget, når du skriver testen — da testkoden og det forventede output er placeret lige ved siden af hinanden. Men hvis nogle testdata overstiger en håndfuld linjer i længden, eller hvis de samme data skal bruges flere steder, kan det stadig være værd at flytte dem til sin egen fil.

rå strenge

nyt i hurtige 5, rå strenge gør det muligt for os at slukke for alle dynamiske streng bogstavelige funktioner (såsom interpolation og tolkning af specialtegn, som\n) til fordel for simpelthen at behandle en bogstavelig som en rå sekvens af tegn. Rå strenge defineres ved at omgive en streng bogstavelig med pundtegn (eller “hashtags”, som børnene kalder dem):

let rawString = #"Press "Continue" to close this dialog."#

ligesom hvordan vi ovenfor brugte en multiline bogstavelig til at definere testdata, er råstrengslitteraler særligt nyttige, når vi vil indlejre strenge, der skal indeholde specialtegn — såsom anførselstegn eller tilbageslag. Her er et andet testrelateret eksempel, hvor vi bruger en rå streng bogstavelig til at definere en JSON-streng til at kode en User forekomst fra:

class UserTests: XCTestCase { func testDecoding() throws { let json = #"{"id": 37, "name": "John"}"# let data = Data(json.utf8) let user = try data.decoded() as User XCTAssertEqual(user.id, 37) XCTAssertEqual(user.name, "John") }}

ovenfor bruger vi den type inferens-baserede afkodning API fra “Type inferens-drevet serialisering i hurtig”.

mens rå strenge deaktiverer funktioner som strenginterpolering som standard, er der en måde at tilsidesætte det ved at tilføje et andet pundtegn lige efter interpolationens førende backslash — som dette:

extension URL { func html(withTitle title: String) -> String { return #"<a href="\#(absoluteString)">\#(title)</a>"# }}

endelig er rå strenge også særligt nyttige, når man fortolker en streng ved hjælp af en bestemt syntaks, især hvis denne syntaks er stærkt afhængig af tegn, der normalt skal undslippes inden for en streng bogstavelig — såsom regulære udtryk. Ved at definere regulære udtryk ved hjælp af rå strenge er det ikke nødvendigt at undslippe, hvilket giver os udtryk, der er så læsbare som de får:

// This expression matches all words that begin with either an// uppercase letter within the A-Z range, or with a number.let regex = try NSRegularExpression( pattern: #"(()|(\d))\w+"#)

selv med ovenstående forbedringer er det tvivlsomt, hvor let at læse (og debug) regulære udtryk er — især når de bruges i forbindelse med et meget typesikkert sprog som hurtig. Det vil højst sandsynligt komme ned til en given udviklers tidligere erfaring med regulære udtryk, uanset om de foretrækker dem frem for at implementere flere brugerdefinerede strengparsing algoritmer, direkte i hurtig.

udtrykker værdier ved hjælp af strenglitteraler

mens alle strenglitteraler omdannes tilString værdier som standard kan vi også bruge dem til også at udtrykke brugerdefinerede værdier. Ligesom vi tog et kig på i” Type-safe identifikatorer i hurtig”, tilføje streng bogstavelig støtte til en af vores egne typer kan lade os opnå øget type sikkerhed, uden at ofre bekvemmeligheden ved at bruge bogstaver.lad os sige, at vi har defineret enSearchable protokol til at fungere som API til søgning i enhver form for database eller underliggende lager, som vores app bruger — og at vi bruger enQuery enum til at modellere forskellige måder at udføre en sådan søgning på:

protocol Searchable { associatedtype Element func search(for query: Query) -> }enum Query { case matching(String) case notMatching(String) case matchingAny()}

ovenstående tilgang giver os en masse magt og fleksibilitet med hensyn til, hvordan vi udfører hver søgning, men den mest almindelige brugssag er sandsynligvis stadig den enkleste — søgning efter elementer, der matcher en given streng — og det ville være rigtig rart, hvis vi var i stand til at gøre det ved hjælp af en streng bogstavelig.

den gode nyhed er, at vi kan få det til at ske, mens vi stadig holder ovenstående API helt intakt, ved at gøre Query i overensstemmelse med ExpressibleByStringLiteral:

extension Query: ExpressibleByStringLiteral { init(stringLiteral value: String) { self = .matching(value) }}

på den måde er vi nu fri til at udføre matchende søgninger uden at skulle oprette enQueryværdi manuelt — alt hvad vi skal gøre er at passere en streng bogstavelig, som om API ‘ en, vi kalder, faktisk accepterede enString direkte. Her bruger vi denne mulighed til at implementere en test, der verificerer, at en UserStorage type korrekt implementerer sin søgefunktionalitet:

class UserStorageTests: XCTestCase { func testSearch() { let storage = UserStorage.inMemory let user = User(id: 3, name: "Amanda") storage.insert(user) let matches = storage.search(for: "anda") XCTAssertEqual(matches, ) }}

brugerdefinerede streng bogstavelige udtryk kan i mange situationer lade os undgå at skulle vælge mellem typesikkerhed og bekvemmelighed, når vi arbejder med strengbaserede typer, såsom forespørgsler og identifikatorer. Det kan være et godt værktøj at bruge for at opnå et API-design, der skaleres godt fra den enkleste brugssag, hele vejen til at dække kantkasser og tilbyde mere strøm og tilpasningsevne, når det er nødvendigt.

brugerdefineret interpolation

en ting, som alle “smag” af hurtige strenglitteraler har til fælles, er deres støtte til interpolerende værdier. Mens vi altid har været i stand til at tilpasse, hvordan en given type interpoleres ved at overholde CustomStringConvertible — hurtig 5 introducerer nye måder at implementere brugerdefinerede API ‘ er lige oven på strenginterpolationsmotoren.

lad os som et eksempel sige, at vi vil gemme en given streng ved eventuelt at anvende et præfiks og suffiks til det. Ideelt set vil vi gerne blot interpolere disse værdier for at danne den endelige streng, som denne:

func save(_ text: String, prefix: String?, suffix: String?) { let text = "\(prefix)\(text)\(suffix)" textStorage.store(text)}

men da begge prefix og suffix er valgfrie, vil det blot at bruge deres beskrivelse ikke producere det resultat, vi leder efter — og kompilatoren vil endda give os en advarsel:

String interpolation produces a debug description for an optional value

mens vi altid har mulighed for at pakke hver af disse to muligheder ud, før vi interpolerer dem, lad os se på, hvordan vi kunne gøre begge disse ting på en gang ved hjælp af brugerdefineret interpolation. Vi starter med at udvide String.StringInterpolation med en ny appendInterpolation overbelastning, der accepterer enhver valgfri værdi:

extension String.StringInterpolation { mutating func appendInterpolation<T>(unwrapping optional: T?) { let string = optional.map { "\($0)" } ?? "" appendLiteral(string) }}

ovenstående unwrapping: parameteretiket er vigtigt, da det er det, vi vil bruge til at Bed kompilatoren om at bruge den specifikke interpolationsmetode — sådan:

func save(_ text: String, prefix: String?, suffix: String?) { let text = "\(unwrapping: prefix)\(text)\(unwrapping: suffix)" textStorage.store(text)}

selvom det bare er syntaktisk sukker, ser ovenstående virkelig pænt ud! Imidlertid ridser det næppe overfladen af, hvad brugerdefinerede strenginterpoleringsmetoder kan gøre. De kan være både generiske og ikke-generiske, acceptere et hvilket som helst antal argumenter, bruge standardværdier og stort set alt andet, som “normale” metoder kan gøre.

Her er et andet eksempel, hvor vi aktiverer vores metode til konvertering af en URL til et HTML-link fra før til også at blive brugt i forbindelse med strenginterpolering:

extension String.StringInterpolation { mutating func appendInterpolation(linkTo url: URL, _ title: String) { let string = url.html(withTitle: title) appendLiteral(string) }}

med ovenstående på plads kan vi nu nemt generere HTML-links fra en URL som denne:

webView.loadHTMLString( "If you're not redirected, \(linkTo: url, "tap here").", baseURL: nil)

det seje ved brugerdefineret strenginterpolering er, hvordan kompilatoren tager hver af vores appendInterpolation metoder og oversætter dem til tilsvarende interpolations — API ‘ er-hvilket giver os fuld kontrol over, hvordan opkaldssiden vil se ud, for eksempel ved at fjerne eksterne parameteretiketter, som vi gjorde for title ovenfor.

vi fortsætter med at undersøge flere måder at bruge brugerdefineret strenginterpolering på, for eksempel med tilskrevne strenge og andre slags tekstmetadata, i kommende artikler.

Support Hurtig af Sundell ved at tjekke denne sponsor:

Essential Developer

Essential Developer: Deltag i et gratis online crashkursus for iOS-udviklere, der ønsker at blive seniorudviklere med sort bælte — det vil sige: opnå et ekspertniveau af praktiske færdigheder og blive en del af de højest betalte udviklere i verden.

konklusion

mens nogle af hurtige mere avancerede streng bogstavelige funktioner kun er virkelig nyttige i meget specifikke situationer, som dem i denne artikel, er det rart at have dem tilgængelige, når det er nødvendigt — især da det er muligt at undgå dem fuldstændigt og kun bruge strenge"the old-fashioned way".String literals er et andet område, hvor hurtig protokolorienterede design virkelig skinner. Ved at delegere meget af, hvordan bogstaver fortolkes og håndteres til implementatorer af protokoller, snarere end hårdkodning af disse adfærd i selve kompilatoren, vi som tredjepartsudviklere er i stand til stærkt at tilpasse den måde, bogstaver håndteres på-mens vi stadig holder standardindstillingerne så enkle som de kan være.