Articles

String literals in Swift

kyky ilmaista perusarvoja, kuten merkkijonoja ja kokonaislukuja, käyttäen inline literals on olennainen ominaisuus useimmissa ohjelmointikielissä. Vaikka monilla muilla kielillä on tuki kääntäjäänsä leivotuille literaaleille, Swift noudattaa huomattavasti dynaamisempaa lähestymistapaa — käyttää omaa tyyppijärjestelmäänsä määritelläkseen, miten eri literaaleja tulisi käsitellä protokollien avulla.

tällä viikolla keskitytään erityisesti string literaleihin tarkastelemalla niitä monin eri tavoin ja sitä, miten me — Swiftin erittäin protokollapainotteisen muotoilun kautta-pystymme muokkaamaan literaalien tulkintaa, mikä antaa meille mahdollisuuden tehdä todella mielenkiintoisia asioita.

Essential Developer

Essential Developer: Join a free online crash course for iOS developers who want to become black-belt senior developers-that is: saavuttaa asiantuntevan tason käytännön taitoja ja tulla osaksi korkeimmin palkattuja kehittäjiä maailmassa.

perusasiat

aivan kuten monissa muissakin kielissä, Swift-kielet ilmaistaan lainausmerkkien ympäröimillä kirjaimilla — ja ne voivat sisältää sekä erikoissarjoja (kuten newlines), karanneita merkkejä ja interpoloituja arvoja:

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

vaikka edellä käytetyt ominaisuudet tarjoavat meille jo paljon joustavuutta, ja ne todennäköisesti riittävät valtaosaan käyttötapauksista, on tilanteita, joissa tehokkaammat tavat ilmaista kirjaimellisuutta voivat tulla tarpeeseen. Katsotaanpa katsomaan joitakin niistä, alkaen kun meidän täytyy määritellä merkkijono, joka sisältää useita rivejä tekstiä.

moniriviset literaalit

vaikka mikä tahansa standardijonojen kirjaimisto voidaan jakaa monirivisiksi käyttäen \n, se ei ole aina käytännöllistä — varsinkin jos halutaan määritellä suurempi tekstinpätkä inline-kirjaimeksi.

onneksi Swift 4: n jälkeen on myös mahdollista määritellä moniriviset kielilitrat käyttämällä kolmea lainausmerkkiä yhden sijaan. Esimerkiksi, tässä käytämme tätä kykyä tulostaa ohjetekstin Swift script, jos käyttäjä ei läpäissyt mitään argumentteja kutsuessaan sitä komentoriviltä:

// 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)}

edellä hyödynnetään sitä, että moniriviset kieliluvut säilyttävät tekstinsä sisennyksen suhteessa päättyvään lainausmerkkien joukkoon alareunassa. Niiden avulla voimme myös paljon vapaammin käyttää niiden sisällä olevia sitomattomia lainausmerkkejä, koska ne on määritelty kolmen lainausmerkin joukolla, jolloin kirjaimellisten rajojen epäselvyys on paljon vähäisempää.

molemmat edellä mainitut kaksi ominaisuutta tekevät multirine literalsista oivan työkalun inline HTML: n määrittelyyn — esimerkiksi jonkinlaisessa web — sivun generointityökalussa tai renderöidessä sovelluksen sisällön osia web-näkymiä käyttäen-näin:

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> """ }}

yllä oleva tekniikka voi olla todella hyödyllinen myös merkkijonopohjaisten testitietojen määrittelyssä. Esimerkiksi, sanotaan, että sovelluksemme asetukset on vietävä XML, ja että haluamme kirjoittaa testi, joka varmistaa, että toiminnallisuus. Sen sijaan, että määrittelisimme XML: n, jonka haluamme tarkistaa erillisessä tiedostossa — voimme käyttää monirivistä merkkijonoa kirjaimellisesti inline testiimme:

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> """) }}

etuna testitiedon määrittelemisessä inline, kuten edellä, on se, että testin kirjoittamisessa tehdyt virheet on paljon helpompi havaita nopeasti — koska testikoodi ja odotettu ulostulo on sijoitettu aivan vierekkäin. Jos jokin testidata kuitenkin ylittää kourallisen viivoja pituudeltaan tai jos samaa dataa pitää käyttää useassa paikassa, se voi silti olla syytä siirtää omaan tiedostoon.

Raw-merkkijonot

Swift 5: ssä raw-merkkijonojen avulla voidaan sammuttaa kaikki dynaamiset merkkijonojen kirjaimelliset ominaisuudet (kuten interpolointi ja erikoismerkkien tulkitseminen, kuten \n), jolloin kirjaimistoa voidaan yksinkertaisesti käsitellä raakamerkkijonona. Raw Jouset määritellään ympäröimällä merkkijono kirjaimellinen kanssa punta merkkejä (tai ”hashtags”, kuten lapset kutsuvat niitä):

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

aivan kuten edellä käytettiin monirivistä kirjaimistoa testitiedon määrittämiseen, raa ’ at merkkijonot ovat erityisen hyödyllisiä, kun haluamme riviin merkkijonoja, joiden on sisällettävä erikoismerkkejä — kuten lainausmerkkejä tai kääntöviivoja. Tässä on toinen testiin liittyvä esimerkki, jossa käytämme raw-merkkijonon kirjaimistoa JSON-merkkijonon koodaamiseen User instance from:

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") }}

yllä käytämme type inference-based decoding API: a ”Type inference-powered serialization in Swift”.

vaikka raw — merkkijonot poistavat oletusarvoisesti merkkijonojen interpoloinnin kaltaiset ominaisuudet käytöstä, on olemassa tapa ohittaa se lisäämällä toinen pound — merkki heti interpoloinnin johtavan kääntöpuolen jälkeen-kuten tämä:

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

lopuksi, raw-merkkijonot ovat myös erityisen hyödyllisiä tulkittaessa merkkijonoa tietyn syntaksin avulla, varsinkin jos tämä syntaksi perustuu vahvasti merkkeihin, jotka normaalisti täytyisi karata merkkijonon sisällä kirjaimellisesti-kuten säännölliset lausekkeet. Määrittelemällä säännölliset lausekkeet käyttäen raakoja merkkijonoja, ei tarvita pakenemista, jolloin saamme lausekkeet, jotka ovat yhtä luettavia kuin ne saavat:

// 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+"#)

vaikka edellä mainitut parannukset, on kyseenalaista, kuinka helposti luettavia (ja debug) säännölliset lausekkeet ovat — varsinkin kun niitä käytetään Swiftin kaltaisen erittäin tyyppiturvallisen kielen yhteydessä. Se tulee todennäköisesti alas tahansa kehittäjän aiempi kokemus säännöllisiä lausekkeita, onko he mieluummin niitä kuin toteuttaa enemmän mukautettuja merkkijono jäsennysalgoritmeja, suoraan Swift.

ilmaisevat arvot käyttäen merkkijonolukuja

vaikka kaikki merkkijonolukulukuluvut muunnetaan String arvot oletusarvoisesti, Voimme käyttää niitä myös omien arvojen ilmaisemiseen. Kuten otimme tarkastella ”Type-safe identifiers Swift”, lisäämällä merkkijono kirjaimellinen tuki yksi omia tyyppejä voi antaa meille lisätä tyyppi turvallisuutta, uhraamatta mukavuutta käyttämällä literals.

esimerkiksi sanotaan, että olemme määritelleet Searchable protokollan, joka toimii sovelluksemme käyttämän tietokannan tai taustalla olevan tallennustilan HAKURAJAPINTANA-ja että käytämme Query enum mallintaaksemme erilaisia tapoja suorittaa tällainen haku:

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

yllä oleva lähestymistapa antaa meille paljon voimaa ja joustavuutta siihen, miten suoritamme jokaisen haun, mutta yleisin käyttötapaus on silti todennäköisesti yksinkertaisin — tietyn merkkijonon vastaavien elementtien etsiminen — ja olisi todella mukavaa, jos voisimme tehdä sen merkkijonon kirjaimellisella avulla.

hyvä uutinen on se, että voimme tehdä sen, pitäen yllä olevan API: n kuitenkin täysin ehjänä, tekemällä Query mukaiseksi ExpressibleByStringLiteral:

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

näin voimme nyt vapaasti tehdä vastaavia hakuja ilman, että meidän tarvitsee luoda Query arvo manuaalisesti — meidän tarvitsee vain läpäistä merkkijono kirjaimellisesti ikään kuin API, jota kutsumme, todella hyväksyisi String suoraan. Tässä käytämme tätä ominaisuutta toteuttaaksemme testin, joka varmistaa, että UserStorage tyyppi toteuttaa oikein hakutoimintonsa:

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, ) }}

Custom string kirjaimelliset lausekkeet voivat monissa tilanteissa estää meitä valitsemasta tyypin turvallisuuden ja mukavuuden välillä työskennellessämme merkkijonopohjaisten tyyppien, kuten kyselyiden ja tunnisteiden kanssa. Se voi olla hyvä työkalu käyttää, jotta saavutetaan API-suunnittelu, joka skaalautuu hyvin yksinkertaisin käyttötapaus, aina kattaa reuna tapauksissa ja tarjoaa enemmän tehoa ja muokattavuutta tarvittaessa.

Custom interpolointi

yksi asia, joka kaikilla Swiftin kielilukujen ”makuilla” on yhteistä, on niiden tuki interpoloiville arvoille. Vaikka olemme aina voineet muokata, miten tietty tyyppi interpoloidaan mukautumalla CustomStringConvertible — Swift 5 esittelee uusia tapoja toteuttaa mukautettuja sovellusliittymiä aivan merkkijonojen interpolointimoottorin päälle.

esimerkkinä voidaan sanoa, että haluamme tallentaa tietyn merkkijonon käyttämällä valinnaisesti etuliitettä ja päätettä siihen. Ihannetapauksessa haluaisimme yksinkertaisesti interpoloida nämä arvot muodostaaksemme lopullisen merkkijonon, näin:

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

kuitenkin, koska molemmat prefix ja suffix ovat valinnaisia, pelkkä niiden kuvauksen käyttäminen ei tuota etsimäämme tulosta — ja kääntäjä antaa jopa varoituksen:

String interpolation produces a debug description for an optional value

vaikka meillä on aina mahdollisuus avata kumpikin näistä kahdesta vaihtoehdosta ennen niiden interpolointia, katsotaanpa, miten voisimme tehdä molemmat asiat yhdellä kertaa käyttämällä mukautettua interpolointia. Aloitamme laajentamalla String.StringInterpolation uudella appendInterpolation ylikuormitus, joka hyväksyy minkä tahansa valinnaisen arvon:

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

yllä oleva unwrapping: parametrin etiketti on tärkeä, koska se on mitä käytämme kertomaan Kääntäjä käyttää tätä tiettyä interpolointimenetelmää — näin:

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

vaikka se on vain syntaktista sokeria, edellä mainittu näyttää todella siistiltä! Kuitenkin, että tuskin naarmuttaa pintaa, mitä custom merkkijono interpolointi menetelmät voivat tehdä. Ne voivat olla sekä yleisiä että ei-yleisiä, hyväksyä minkä tahansa määrän argumentteja, käyttää oletusarvoja ja melko paljon kaikkea muuta, mitä ”normaalit” menetelmät voivat tehdä.

tässä on toinen esimerkki, jossa mahdollistamme menetelmämme URL-osoitteen muuntamiseksi HTML-linkiksi aikaisemminkin käytettäväksi myös merkkijonojen interpoloinnin yhteydessä:

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

Kun yllä oleva on käytössä, voimme nyt helposti luoda HTML-linkkejä tämänkaltaisesta URL-osoitteesta:

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

Custom string interpoloinnissa on hienoa se, miten kääntäjä ottaa jokaisen appendInterpolation — menetelmämme ja muuntaa ne vastaaviksi interpolointirajapinnoiksi-antaen meille täydellisen kontrollin siitä, miltä puhelusivusto näyttää, esimerkiksi poistamalla ulkoiset parametrietiketit, kuten teimme title edellä.

tutkimme tulevissa artikkeleissa lisää tapoja käyttää muokattua merkkijonojen interpolointia, esimerkiksi merkittyjen merkkijonojen ja muunlaisten tekstien metatietojen avulla.

Support Swift by Sundell by checking out this sponsor:

Essential Developer

Essential Developer: Join a free online crash course for iOS developers who want to become black-belt senior developers — that is: achieve a expert level of practical skills and became of the highest paid developers in the world.

johtopäätös

vaikka jotkut Swiftin kehittyneemmistä merkkijonojen kirjaimellisista ominaisuuksista ovat todella hyödyllisiä vain hyvin erityistilanteissa, kuten tässä artikkelissa, on mukavaa saada ne tarvittaessa käyttöön — varsinkin kun on mahdollista välttää ne kokonaan ja käyttää vain merkkijonoja "the old-fashioned way".

String literals on toinen alue, jossa Swiftin protokollapainotteinen muotoilu todella loistaa. Delegoimalla paljon siitä, miten literals tulkitaan ja käsitellään protokollien toteuttajille, sen sijaan, että koodaamme näitä käyttäytymismalleja Kääntäjässä itsessään, Me kolmannen osapuolen kehittäjinä pystymme voimakkaasti muokkaamaan tapaa, jolla literals käsitellään-pitäen silti oletukset niin yksinkertaisina kuin ne voivat olla.