Strenglitteraler I Swift
å kunne uttrykke grunnleggende verdier, for eksempel strenger og heltall, ved hjelp av inline-bokstaver, er en viktig funksjon i de fleste programmeringsspråk. Men Mens Mange andre språk har støtte for spesifikke bokstaver bakt inn i kompilatoren, Tar Swift en mye mer dynamisk tilnærming-ved hjelp av sitt eget typesystem for å definere hvordan ulike bokstaver skal håndteres, gjennom protokoller.Denne uken, la oss fokusere på strenglitteraler spesielt, ved å ta en titt på de mange forskjellige måtene de kan brukes på, og hvordan Vi — gjennom Swifts høyt protokollorienterte design-er i stand til å tilpasse måten litteraler tolkes, noe som lar oss gjøre noen virkelig interessante ting.
Essential Developer: Bli en gratis online lynkurs for iOS utviklere som ønsker å bli svart belte senior utviklere-det er: oppnå et ekspertnivå av praktiske ferdigheter og bli en del av de høyest betalte utviklerne i verden.
grunnleggende
På samme måte som På mange andre språk uttrykkes Swift-strenger gjennom bokstaver omgitt av anførselstegn — og kan inneholde både spesielle sekvenser( for eksempel nye linjer), rømte tegn og interpolerte verdier:
let string = "\(user.name) says \"Hi!\"\nWould you like to reply?"// John says "Hi!"// Would you like to reply?
mens funksjonene som brukes ovenfor allerede gir oss stor fleksibilitet, og er mest sannsynlig nok for de aller fleste brukstilfeller, er det situasjoner der kraftigere måter å uttrykke bokstaver kan komme til nytte. La oss ta en titt på noen av dem, starter med når vi trenger å definere en streng som inneholder flere linjer med tekst.
Multiline literals
selv om en standard streng bokstavelig kan brytes opp i flere linjer ved hjelp av \n
, er det ikke alltid praktisk — spesielt hvis vi ønsker å definere et større stykke tekst som en inline bokstavelig.Heldigvis, Siden Swift 4, kan Vi også definere multiline strenglitteraler ved hjelp av tre anførselstegn i stedet for bare en. For eksempel, her bruker vi den muligheten til å skrive ut en hjelpetekst for Et Swift-skript, hvis brukeren ikke passerte noen argumenter når han påkalte den 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 gjør Vi bruk av det faktum at multiline strenglitteraler bevare tekstens innrykk, i forhold til avslutningssettet av anførselstegn, nederst. De gjør oss også i stand til mye mer fritt bruke unescaped anførselstegn i dem, siden de er definert av et sett med tre anførselstegn, noe som gjør grensene for den bokstavelige mye mindre sannsynlig å bli tvetydig.
begge de ovennevnte to egenskapene gjør multiline-bokstaver til et flott verktøy for å definere inline HTML — for eksempel i en form for nettsidegenereringsverktøy, eller når du gjengir deler av innholdet i en app ved hjelp av webvisninger — slik:
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> """ }}
teknikken ovenfor kan også være veldig nyttig når du definerer strengbaserte testdata. For eksempel, la oss si at appens innstillinger må eksporteres SOM XML, og at vi vil skrive en test som bekrefter den funksjonaliteten. Snarere enn å måtte definere XML som vi ønsker å verifisere mot i en egen fil-vi kan bruke en multiline streng bokstavelig å inline det inn i vår test:
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 med å definere testdata inline, som vi gjør ovenfor, er at det blir mye lettere å raskt oppdage eventuelle feil som gjøres når du skriver testen – siden testkoden og forventet utgang er plassert rett ved siden av hverandre — Men hvis noen testdata overstiger en håndfull linjer i lengde, eller hvis de samme dataene må brukes på flere steder, kan det fortsatt være verdt å flytte den til sin egen fil.
Råstrenger
nytt I Swift 5, råstrenger gjør oss i stand til å slå av alle dynamiske streng bokstavelig funksjoner (for eksempel interpolering, og tolke spesialtegn, som \n
), i favør av bare å behandle en bokstavelig som en rå sekvens av tegn. Rå strenger er definert ved å omgi en streng bokstavelig med pund tegn (eller «hashtags» , som barna kaller dem):
let rawString = #"Press "Continue" to close this dialog."#
akkurat som hvordan vi ovenfor brukte en multiline bokstavelig å definere testdata, rå strenglitteraler er spesielt nyttig når vi ønsker å inline strenger som må inneholde spesialtegn — for eksempel anførselstegn eller skråstreker. Her er et annet testrelatert eksempel, der vi bruker en rå streng bokstavelig for å definere EN json-streng for å kode enUser
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 bruker vi typen inferensbasert dekoding API FRA «type inferensdrevet serialisering I Swift».
mens råstrenger deaktiverer funksjoner som strenginterpolering som standard, er det en måte å overstyre det ved å legge til et annet pund tegn rett etter interpolasjonens ledende tilbakeslag — slik:
extension URL { func html(withTitle title: String) -> String { return #"<a href="\#(absoluteString)">\#(title)</a>"# }}
til Slutt er råstrenger også spesielt nyttige når man tolker en streng ved hjelp av en bestemt syntaks, spesielt hvis den syntaksen er avhengig av tegn som normalt må unngås i en streng bokstavelig — for eksempel regulære uttrykk. Ved å definere regulære uttrykk ved hjelp av råstrenger, er det ikke nødvendig å rømme, noe som gir oss uttrykk som er like lesbare 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 de ovennevnte forbedringene, er det tvilsomt hvor lett å lese (og feilsøke) regulære uttrykk er-spesielt når de brukes i sammenheng med et svært typesikkert språk som Swift. Det vil mest sannsynlig komme ned til en gitt utviklerens tidligere erfaring med regulære uttrykk, uansett om de foretrekker dem over å implementere mer tilpassede strengparsingalgoritmer, direkte i Swift.
Uttrykker verdier ved hjelp av strenglitteraler
mens alle strenglitteraler blir omgjort til String
verdier som standard, kan vi også bruke dem til å uttrykke egendefinerte verdier også. Som vi tok en titt på i «typesikker identifikatorer I Swift», legge streng bokstavelig støtte til en av våre egne typer kan la oss oppnå økt type sikkerhet, uten å ofre bekvemmeligheten av å bruke bokstaver.la Oss for eksempel si at vi har definert EN Searchable
protokoll for Å fungere SOM API for å søke i noen form for database eller underliggende lagring som appen vår bruker — og at vi bruker en Query
enum for å modellere forskjellige måter å utføre et slikt søk på:
protocol Searchable { associatedtype Element func search(for query: Query) -> }enum Query { case matching(String) case notMatching(String) case matchingAny()}
ovennevnte tilnærming gir oss mye kraft og fleksibilitet med hensyn til hvordan vi skal utføre hvert søk, men det vanligste brukssaken er fortsatt sannsynlig å være den enkleste — søker etter elementer som samsvarer med en gitt streng — og det ville være veldig fint om vi kunne gjøre det ved hjelp av en streng bokstavelig.
den gode nyheten er at vi kan få det til å skje, samtidig som API-EN holdes helt intakt, ved å gjøreQuery
i samsvar med ExpressibleByStringLiteral
:
extension Query: ExpressibleByStringLiteral { init(stringLiteral value: String) { self = .matching(value) }}
på den måten er vi nå fri til å utføre matchende søk uten å måtte opprette enQuery
verdi manuelt — alt vi trenger å gjøre er å sende en streng bokstavelig som OM API vi ringer faktisk akseptert enString
direkte. Her bruker vi den evnen til å implementere en test som bekrefter at en UserStorage
type implementerer sin søkefunksjonalitet riktig:
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, ) }}
Tilpassede strenguttrykk kan i mange situasjoner la Oss unngå å måtte velge mellom typesikkerhet og bekvemmelighet når vi arbeider med strengbaserte typer, for eksempel spørringer og identifikatorer. DET kan være et flott verktøy å bruke for å oppnå EN API-design som skalerer godt fra den enkleste brukssaken, helt til å dekke kantsaker og tilby mer kraft og tilpassbarhet når det trengs.
Tilpasset interpolering
en ting som alle «smaker» Av Swift-strenglitteraler har til felles, er deres støtte for interpoleringsverdier. Mens Vi alltid har vært i stand til å tilpasse hvordan en gitt type interpoleres ved å overholde CustomStringConvertible
— Swift 5 introduserer nye måter å implementere tilpassede Apier rett på toppen av strenginterpolasjonsmotoren.
Som et eksempel, la oss si at vi vil lagre en gitt streng ved å eventuelt bruke et prefiks og suffiks til det. Ideelt sett vil vi bare interpolere disse verdiene for å danne den endelige strengen, slik:
func save(_ text: String, prefix: String?, suffix: String?) { let text = "\(prefix)\(text)\(suffix)" textStorage.store(text)}
siden både prefix
og suffix
er alternativer, vil det ikke bare å bruke beskrivelsen gi det resultatet vi leter etter — og kompilatoren vil til og med gi oss en advarsel:
String interpolation produces a debug description for an optional value
mens vi alltid har muligheten Til å pakke Ut Hver av De to alternativene før de interpoleres, la oss se på hvordan vi kunne gjøre begge disse tingene på en gang ved hjelp av tilpasset interpolering. Vi begynner med å utvide String.StringInterpolation
med en ny appendInterpolation
overbelastning som aksepterer valgfri verdi:
extension String.StringInterpolation { mutating func appendInterpolation<T>(unwrapping optional: T?) { let string = optional.map { "\($0)" } ?? "" appendLiteral(string) }}
ovennevnte unwrapping:
parameteretikett er viktig, da det er det vi skal bruke til å fortelle kompilatoren til å bruke den spesifikke interpoleringsmetoden — slik:
func save(_ text: String, prefix: String?, suffix: String?) { let text = "\(unwrapping: prefix)\(text)\(unwrapping: suffix)" textStorage.store(text)}
selv om det bare er syntaktisk sukker, ser det ovenfor veldig pent ut! Men det knapt riper overflaten av hva tilpassede strenginterpoleringsmetoder kan gjøre. De kan være både generiske og ikke-generiske, godta et hvilket som helst antall argumenter, bruke standardverdier, og stort sett alt annet som «normale» metoder kan gjøre.
her er et annet eksempel der vi aktiverer vår metode for å konvertere EN URL til EN HTML-lenke fra før for å også brukes i sammenheng med strenginterpolering:
extension String.StringInterpolation { mutating func appendInterpolation(linkTo url: URL, _ title: String) { let string = url.html(withTitle: title) appendLiteral(string) }}
med ovennevnte på plass kan vi nå enkelt generere HTML-koblinger fra EN URL som dette:
webView.loadHTMLString( "If you're not redirected, \(linkTo: url, "tap here").", baseURL: nil)
den kule tingen om tilpasset strenginterpolering er hvordan kompilatoren tar hver av våre appendInterpolation
metoder og oversetter dem til tilsvarende interpolerings — Apier-noe som gir oss full kontroll over hvordan anropsstedet vil se ut, for eksempel ved å fjerne eksterne parameteretiketter, som vi gjorde for title
ovenfor.
Vi vil fortsette å se på flere måter å bruke tilpasset strenginterpolering, for eksempel med tilskrevne strenger og andre typer tekstmetadata, i kommende artikler.
Støtte Swift Av Sundell ved å sjekke ut denne sponsor:
Essential Developer: Bli med en gratis online lynkurs for iOS utviklere som ønsker å bli svart belte senior utviklere-det vil si: oppnå en ekspert nivå av praktiske ferdigheter og bli en del av de best betalte utviklere i verden.
Konklusjon
mens Noen Av Swifts mer avanserte streng bokstavelig evner er bare veldig nyttig i svært spesifikke situasjoner, slik som de i denne artikkelen, er det fint å ha dem tilgjengelig når det trengs-spesielt siden det er mulig å helt unngå dem og bare bruke strenger "the old-fashioned way"
.
Strenglitteraler er et annet område Der Swifts protokollorienterte design virkelig skinner. Ved å delegere mye av hvordan litteraler tolkes og håndteres til implementorer av protokoller, i stedet for hardkoding av atferdene i kompilatoren selv, kan vi som tredjepartsutviklere tilpasse måten litteraler håndteres på-samtidig som standardverdiene holdes så enkle som de kan være.