Articles

String literálok a Swift-ben

az alapvető értékek, például a karakterláncok és egész számok kifejezése, inline literálok használatával a legtöbb programozási nyelv alapvető jellemzője. Míg azonban sok más nyelv támogatja a fordítójába sütött konkrét literálokat, a Swift sokkal dinamikusabb megközelítést alkalmaz — saját típusú rendszerét használva meghatározza, hogy a különféle literálokat hogyan kell kezelni, protokollokon keresztül.

Ezen a héten különösen a string literálokra összpontosítsunk, figyelembe véve a sokféle felhasználási módot, és azt, hogy — a Swift erősen protokoll-orientált kialakításán keresztül — hogyan tudjuk testreszabni a literálok értelmezését, ami lehetővé teszi számunkra, hogy igazán érdekes dolgokat tegyünk.

Essential Developer

Essential Developer: csatlakozzon egy ingyenes online összeomlási tanfolyamhoz az iOS Fejlesztők számára, akik fekete öves vezető fejlesztőkké akarnak válni — azaz: elérni a szakértői szintű gyakorlati készségek és részévé válik a legjobban fizetett fejlesztők a világon.

az alapok

mint sok más nyelvben, a Swift karakterláncokat idézőjelekkel körülvett literálok fejezik ki — és tartalmazhatnak mind speciális szekvenciákat (például újsorokat), megszökött karaktereket és interpolált értékeket:

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

míg a fent használt funkciók már nagy rugalmasságot biztosítanak számunkra, és valószínűleg elégségesek a használati esetek túlnyomó többségéhez, vannak olyan helyzetek, amikor a literálok kifejezésének hatékonyabb módjai jól jöhetnek. Vessünk egy pillantást ezekre, kezdve azzal, amikor meg kell határoznunk egy több szöveget tartalmazó karakterláncot.

többsoros literálok

bár bármely szabványos string literál több sorra bontható a \n használatával, ez nem mindig praktikus — különösen, ha egy nagyobb szövegdarabot inline literálként akarunk meghatározni.

szerencsére a Swift 4 óta képesek vagyunk többsoros karakterlánc literálokat is meghatározni három idézőjel helyett csak egy. Például itt használjuk ezt a képességet egy Swift szkript súgószövegének kiadására, arra az esetre, ha a felhasználó nem adott át argumentumokat, amikor a parancssorba hívta:

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

fent azt a tényt használjuk, hogy a többsoros karakterlánc literálok megőrzik szövegük behúzását, az idézőjelek végződéséhez viszonyítva, alul. Azt is lehetővé teszik számunkra, hogy sokkal szabadabban használjuk az elkerülhetetleneket idézőjelek bennük, mivel három idézőjel halmaza határozza meg őket, így a szó szerinti határok sokkal kevésbé valószínű, hogy kétértelművé válnak.

mind a két fenti jellemzők teszik multiline literals egy nagyszerű eszköz meghatározására inline HTML-például valamilyen formában weblap generációs eszköz, vagy amikor renderelés részei egy alkalmazás tartalmát a webes nézetek — mint ez:

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

a fenti technika is nagyon hasznos lehet meghatározásakor string-alapú vizsgálati adatok. Tegyük fel például, hogy alkalmazásunk beállításainak EXPORTÁLHATÓNAK kell lenniük XML-ként, és hogy egy tesztet akarunk írni, amely ellenőrzi ezt a funkciót. Ahelyett, hogy meg kellene határoznunk az XML-t, amelyet külön fájlban szeretnénk ellenőrizni — használhatunk egy többsoros karakterlánc literált a tesztbe való beillesztéshez:

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

a tesztadatok inline meghatározásának előnye, mint fent, az, hogy sokkal könnyebb gyorsan észrevenni a teszt írásakor elkövetett hibákat — mivel a tesztkód és a várt kimenet közvetlenül egymás mellé kerül. Ha azonban egyes tesztadatok meghaladják a maroknyi sor hosszát, vagy ha ugyanazokat az adatokat több helyen kell használni, akkor is érdemes áthelyezni a saját fájljába.

nyers karakterláncok

újdonság a Swift 5-ben, a nyers karakterláncok lehetővé teszik számunkra, hogy kikapcsoljuk az összes dinamikus karakterlánc-literál funkciót (például interpolációt és speciális karakterek értelmezését, például \n), annak érdekében, hogy a literálokat egyszerűen nyers karaktersorozatként kezeljük. A nyers húrokat úgy határozzák meg, hogy egy karakterláncot fontjelekkel (vagy “hashtagekkel” vesznek körül, ahogy a gyerekek hívják őket):

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

csakúgy, mint ahogy fentebb egy többsoros literált használtunk a tesztadatok meghatározásához, a nyers karakterlánc literálok különösen akkor hasznosak, ha olyan karakterláncokat akarunk beilleszteni, amelyeknek speciális karaktereket kell tartalmazniuk — például idézőjeleket vagy fordított perjeleket. Itt van egy másik teszthez kapcsolódó példa, amelyben egy raw string literal segítségével definiálunk egy JSON karakterláncot a User példány kódolásához:

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

fent a type inference-based decoding API-t használjuk a “Type inference-powered serialization in Swift” – ből.

míg a nyers karakterláncok alapértelmezés szerint letiltják az olyan funkciókat, mint a karakterlánc — interpoláció, van egy módja annak, hogy ezt felülírjuk egy másik fontjel hozzáadásával közvetlenül az interpoláció vezető fordított perjelje után — mint ez:

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

végül a nyers karakterláncok különösen hasznosak egy karakterlánc adott szintaxis használatával történő értelmezésekor, különösen, ha ez a szintaxis nagymértékben támaszkodik olyan karakterekre, amelyeket általában el kell kerülni egy karakterlánc literáljában-például reguláris kifejezések. A reguláris kifejezések nyers karakterláncok használatával történő definiálásával nincs szükség menekülésre, így olyan kifejezéseket kapunk, amelyek annyira olvashatók, amennyit csak kapnak:

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

még a fenti fejlesztésekkel is kérdéses, hogy mennyire könnyen olvashatók (és hibakereshetők) a reguláris kifejezések — különösen, ha egy olyan nagyon típusú biztonságos nyelv kontextusában használják, mint a Swift. Valószínűleg az adott Fejlesztő reguláris kifejezésekkel kapcsolatos korábbi tapasztalataira vezethető vissza, függetlenül attól, hogy jobban kedvelik-e őket, mint több egyedi karakterlánc-elemző algoritmust, közvetlenül a Swift-ben.

értékek kifejezése karakterlánc-literálok használatával

míg az összes karakterlánc-literál String értékek alapértelmezés szerint az egyéni értékek kifejezésére is felhasználhatjuk őket. Mint vettünk egy pillantást a “Type-safe identifiers in Swift”, hozzátéve string literal támogatást az egyik saját típusú segítségével érhetjük el fokozott típusbiztonság, feláldozása nélkül a kényelem használata literálok.

tegyük fel például, hogy definiáltunk egySearchable protokollt, amely API-ként működik bármilyen adatbázis vagy mögöttes tároló kereséséhez, amelyet alkalmazásunk használ — és hogy egy Query enum-ot használunk egy ilyen keresés különböző módjainak modellezéséhez:

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

a fenti megközelítés sok energiát és rugalmasságot ad nekünk az egyes keresések végrehajtásában, de a leggyakoribb felhasználási eset továbbra is valószínűleg a legegyszerűbb — egy adott karakterláncnak megfelelő elemek keresése—, és nagyon jó lenne, ha ezt egy string literal segítségével tudnánk megtenni.

a jó hír az, hogy ezt meg tudjuk valósítani, miközben a fenti API-t továbbra is teljesen érintetlenül tartjuk, azáltal, hogy Query megfelel a ExpressibleByStringLiteral:

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

így most már szabadon elvégezhetjük a megfelelő kereséseket anélkül, hogy létre kellene hoznunk egy Queryértéket manuálisan — mindössze annyit kell tennünk, hogy átadunk egy string literal-t, mintha az általunk hívott API valóban elfogadott volna egy String közvetlenül. Itt ezt a képességet használjuk egy teszt végrehajtására, amely ellenőrzi, hogy a UserStorage típus helyesen valósítja meg keresési funkcióját:

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

Az egyéni karakterlánc-szó szerinti kifejezések sok esetben lehetővé teszik, hogy ne kelljen választanunk a típusbiztonság és a kényelem között, amikor karakterlánc-alapú típusokkal dolgozunk, például lekérdezésekkel és azonosítókkal. Nagyszerű eszköz lehet annak érdekében, hogy olyan API-kialakítást érjünk el, amely jól skálázódik a legegyszerűbb használati esetektől egészen az él tokok lefedéséig, és szükség esetén nagyobb teljesítményt és testreszabhatóságot kínál.

egyéni interpoláció

egy dolog, hogy az összes “ízek” Swift string literálok közös az, hogy támogatja az interpolálás értékeket. Bár mindig is képesek voltunk testre szabni egy adott típus interpolálását a CustomStringConvertible — A Swift 5 új módszereket vezet be az egyéni API-k végrehajtására közvetlenül a karakterlánc interpolációs motor tetején.

például tegyük fel, hogy egy adott karakterláncot el akarunk menteni úgy, hogy opcionálisan előtagot és utótagot alkalmazunk rá. Ideális esetben egyszerűen interpolálni szeretnénk ezeket az értékeket, hogy létrehozzuk a végső karakterláncot, így:

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

mivel azonban mindkettő prefix és suffix opcionálisak, egyszerűen a leírásuk használata nem eredményezi a keresett eredményt — és a fordító még figyelmeztetést is ad nekünk:

String interpolation produces a debug description for an optional value

bár mindig lehetőségünk van kibontani a két opció mindegyikét, mielőtt interpolálnánk őket, vessünk egy pillantást arra, hogyan tehetnénk meg ezeket a dolgokat egyszerre az egyéni interpoláció segítségével. Kezdjük azzal, hogy kiterjesztjük a String.StringInterpolation egy új appendInterpolation túlterhelést, amely bármilyen opcionális értéket Elfogad:

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

a fenti unwrapping: a paramétercímke fontos, mivel ezt fogjuk használni a fordító az adott interpolációs módszer használatához — így:

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

bár ez csak szintaktikai cukor, a fentiek nagyon szépnek tűnnek! Ez azonban alig karcolja meg annak a felületét, amit az egyedi karakterlánc-interpolációs módszerek megtehetnek. Lehetnek általánosak és nem általánosak is, elfogadhatnak tetszőleges számú argumentumot, használhatnak alapértelmezett értékeket, és nagyjából bármi mást, amit a “normál” módszerek megtehetnek.

itt van egy másik példa, amelyben engedélyezzük az URL-ek HTML-hivatkozássá történő átalakításának módszerét a string interpoláció összefüggésében is:

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

A fentiek mellett most már könnyen létrehozhatunk HTML-linkeket egy ilyen URL-ből:

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

az egyéni karakterlánc — interpolációban az a klassz dolog, hogy a fordító hogyan veszi az egyes appendInterpolation metódusokat, és lefordítja őket megfelelő interpolációs API-kba-teljes ellenőrzést adva a hívás webhelyének kinézete felett, például külső paramétercímkék eltávolításával, mint mi a title felett.

a következő cikkekben folytatjuk az egyéni karakterlánc-interpoláció használatának további módjait, például a hozzárendelt karakterláncokkal és más típusú szöveges metaadatokkal.

támogassa a Swift by Sundell-t, ha megnézi ezt a szponzort:

Essential Developer

Essential Developer: csatlakozzon egy ingyenes online gyorstalpaló tanfolyamhoz iOS Fejlesztők számára, akik fekete öves vezető fejlesztőkké akarnak válni — vagyis: szakértői szintű gyakorlati készségeket érjenek el, és a világ legjobban fizetett fejlesztőinek részévé váljanak.

következtetés

míg a Swift fejlettebb string literal képességei csak nagyon speciális helyzetekben hasznosak, mint például a cikkben, jó, ha szükség esetén rendelkezésre állnak — különösen azért, mert teljesen el lehet kerülni őket, és csak stringeket lehet használni "the old-fashioned way".

A String literals egy másik terület, ahol a Swift protokoll-orientált kialakítása valóban ragyog. Azáltal, hogy a literálok értelmezésének és kezelésének nagy részét átruházzuk a protokollok megvalósítóira, ahelyett, hogy ezeket a viselkedéseket magában a fordítóban kódolnánk, mi, mint harmadik féltől származó fejlesztők, képesek vagyunk nagymértékben testreszabni a literálok kezelésének módját-miközben az alapértelmezett értékeket továbbra is a lehető legegyszerűbben tartjuk.