Literali String în Swift
a fi capabil să exprime valori de bază, cum ar fi șiruri și numere întregi, folosind literali inline este o caracteristică esențială în majoritatea limbajelor de programare. Cu toate acestea, în timp ce multe alte limbi au suportul pentru literali specifici copți în compilatorul lor, Swift adoptă o abordare mult mai dinamică — folosind propriul sistem de tip pentru a defini modul în care ar trebui manipulate diferite literale, prin protocoale.
în această săptămână, să ne concentrăm în special pe literalele cu coarde, aruncând o privire asupra numeroaselor moduri diferite în care pot fi utilizate și asupra modului în care noi — prin designul extrem de orientat spre protocol al Swift-suntem capabili să personalizăm modul în care literalele sunt interpretate, ceea ce ne permite să facem câteva lucruri cu adevărat interesante.
Dezvoltator esențial: Alăturați-vă unui curs online gratuit pentru dezvoltatorii iOS care doresc să devină Dezvoltatori seniori cu centură neagră — adică: obțineți un nivel expert de abilități practice și deveniți parte a celor mai bine plătiți dezvoltatori din lume.
elementele de bază
la fel ca în multe alte limbi, șirurile Swift sunt exprimate prin literali înconjurați de ghilimele — și pot conține atât secvențe speciale (cum ar fi linii noi), caractere evadate și valori interpolate:
let string = "\(user.name) says \"Hi!\"\nWould you like to reply?"// John says "Hi!"// Would you like to reply?
în timp ce caracteristicile utilizate mai sus ne oferă deja multă flexibilitate și sunt cel mai probabil suficiente pentru marea majoritate a cazurilor de utilizare, există situații în care modalități mai puternice de exprimare literală pot fi utile. Să aruncăm o privire la unele dintre acestea, începând cu momentul în care trebuie să definim un șir care conține mai multe linii de text.
literale multilinie
deși orice șir literal standard poate fi împărțit în mai multe linii folosind\n
, acest lucru nu este întotdeauna practic — mai ales dacă dorim să definim o bucată mai mare de text ca un literal inline.
Din fericire, Din moment ce Swift 4, suntem, de asemenea, posibilitatea de a defini literale șir multilinie folosind trei ghilimele în loc de doar unul. De exemplu, aici folosim această capacitate pentru a afișa un text de ajutor pentru un script Swift, în cazul în care utilizatorul nu a transmis niciun argument atunci când l-a invocat pe linia de comandă:
// 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)}
Mai sus folosim faptul că literalii șirurilor multilinie păstrează indentarea textului lor, în raport cu setul de ghilimele de încheiere, în partea de jos. Ele ne permit, de asemenea, să folosim mult mai liber ghilimele necapturate în interiorul lor, deoarece sunt definite de un set de trei ghilimele, făcând limitele literalului mult mai puțin susceptibile de a deveni ambigue.
ambele caracteristici de mai sus fac din multiline literals un instrument excelent pentru definirea HTML — ului inline — de exemplu într-o formă de instrument de generare a paginilor web sau atunci când redați părți din conținutul unei aplicații folosind vizualizări web-cum ar fi:
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> """ }}
tehnica de mai sus poate fi, de asemenea, foarte utilă atunci când definiți date de testare bazate pe șiruri. De exemplu, să presupunem că setările aplicației noastre trebuie să fie exportabile ca XML și că dorim să scriem un test care să verifice această funcționalitate. Mai degrabă decât să definim XML — ul pe care dorim să — l verificăm într-un fișier separat-putem folosi un șir multilinie literal pentru a-l alinia în testul nostru:
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> """) }}
beneficiul definirii datelor de testare în linie, așa cum facem mai sus, este că devine mult mai ușor să identificăm rapid orice erori făcute la scrierea testului-deoarece codul de testare și ieșirea așteptată sunt plasate unul lângă celălalt. Cu toate acestea, dacă unele date de testare depășesc o mână de linii în lungime sau dacă aceleași date trebuie utilizate în mai multe locuri, poate merita să le mutați în propriul fișier.
șiruri brute
nou în Swift 5, șirurile brute ne permit să dezactivăm toate caracteristicile literale dinamice ale șirului (cum ar fi interpolarea și interpretarea caracterelor speciale, cum ar fi\n
), în favoarea tratării pur și simplu a literalului ca o secvență brută de caractere. Șirurile brute sunt definite prin înconjurarea unui șir literal cu semne de lire sterline (sau „hashtags”, așa cum le numesc copiii):
let rawString = #"Press "Continue" to close this dialog."#
la fel cum am folosit mai sus un literal multilinie pentru a defini datele de testare, literalele șirurilor brute sunt deosebit de utile atunci când dorim să șirurile de linie care trebuie să conțină caractere speciale — cum ar fi ghilimele sau backslash-urile. Iată un alt exemplu legat de test, în care folosim un șir raw literal pentru a defini un șir JSON pentru a codifica o instanță User
din:
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") }}
Mai sus folosim API-ul de decodare bazat pe inferență de tip din „serializarea bazată pe inferență de tip în Swift”.
în timp ce șirurile brute dezactivează în mod implicit caracteristici precum interpolarea șirurilor, există o modalitate de a suprascrie acest lucru adăugând un alt semn de lire imediat după backslash — ul principal al interpolării — astfel:
extension URL { func html(withTitle title: String) -> String { return #"<a href="\#(absoluteString)">\#(title)</a>"# }}
în cele din urmă, șirurile brute sunt, de asemenea, deosebit de utile atunci când interpretează un șir folosind o sintaxă specifică, mai ales dacă sintaxa respectivă se bazează foarte mult pe caractere care ar trebui în mod normal să fie scăpate într-un șir literal-cum ar fi expresiile regulate. Prin definirea expresiilor regulate folosind șiruri brute, nu este nevoie de scăpare, oferindu — ne expresii care sunt la fel de lizibile ca acestea:
// 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+"#)
chiar și cu îmbunătățirile de mai sus, este discutabil cât de ușor de citit (și depanat) sunt expresiile regulate-mai ales atunci când sunt utilizate în contextul unui limbaj extrem de sigur de tip, cum ar fi Swift. Cel mai probabil se va reduce la experiența anterioară a oricărui dezvoltator dat cu expresii regulate, indiferent dacă preferă sau nu să implementeze algoritmi de analiză a șirurilor personalizate, direct în Swift.
exprimarea valorilor folosind literali șir
în timp ce toate literali șir sunt transformate înString
valori în mod implicit, le putem folosi, de asemenea, pentru a exprima valori personalizate, de asemenea. Așa cum am aruncat o privire în „identificatori siguri de tip în Swift”, adăugarea suportului literal șir la unul dintre tipurile noastre ne poate permite să obținem o siguranță sporită a tipului, fără a sacrifica comoditatea utilizării literalelor.
de exemplu, să spunem că am definit unSearchable
protocol pentru a acționa ca API pentru căutarea oricărui tip de bază de date sau stocare subiacentă pe care o folosește aplicația noastră — și că folosim unQuery
enum pentru a modela diferite moduri de a efectua o astfel de:
protocol Searchable { associatedtype Element func search(for query: Query) -> }enum Query { case matching(String) case notMatching(String) case matchingAny()}
abordarea de mai sus ne oferă multă putere și flexibilitate în ceea ce privește modul în care vom efectua fiecare căutare, dar cel mai frecvent caz de utilizare este încă probabil să fie cel mai simplu — căutarea elementelor care se potrivesc unui șir dat — și ar fi foarte frumos dacă am putea face asta folosind un șir literal.
vestea bună este că putem face acest lucru, păstrând în același timp API-ul de mai sus complet intact, făcândQuery
conforme cuExpressibleByStringLiteral
:
extension Query: ExpressibleByStringLiteral { init(stringLiteral value: String) { self = .matching(value) }}
În acest fel suntem acum liberi să efectuăm căutări potrivite fără a fi nevoie să creăm manual o valoareQuery
— tot ce trebuie să facem este să transmitem un șir literal ca și cum API-ul pe care îl numim a acceptat de fapt unString
direct. Aici folosim această capacitate pentru a implementa un test care verifică dacă un tipUserStorage
implementează corect funcționalitatea de căutare:
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, ) }}
expresiile literale personalizate ale șirurilor pot, în multe situații, să evităm să alegem între siguranța tipului și confortul atunci când lucrăm cu tipuri bazate pe șiruri, cum ar fi interogări și identificatori. Poate fi un instrument excelent de utilizat pentru a realiza un design API care se scalează bine de la cel mai simplu caz de utilizare, până la acoperirea cazurilor de margine și oferind mai multă putere și personalizare atunci când este necesar.
interpolare personalizată
Un lucru pe care toate „aromele” Literalelor Swift string îl au în comun este suportul lor pentru interpolarea valorilor. În timp ce am fost întotdeauna în măsură să particularizeze modul în care un anumit tip este interpolată de conformitate cu CustomStringConvertible
— Swift 5 introduce noi modalități de implementare API-uri personalizate chiar pe partea de sus a motorului de interpolare șir.
de exemplu, să spunem că dorim să salvăm un șir dat aplicând opțional un prefix și un sufix. În mod ideal, am dori să interpolăm pur și simplu aceste valori pentru a forma șirul final, astfel:
func save(_ text: String, prefix: String?, suffix: String?) { let text = "\(prefix)\(text)\(suffix)" textStorage.store(text)}
cu toate acestea, deoarece ambele prefix
și suffix
sunt opționale, simpla utilizare a descrierii lor nu va produce rezultatul pe care îl căutăm — iar compilatorul ne va da chiar și un avertisment:
String interpolation produces a debug description for an optional value
în timp ce avem întotdeauna opțiunea de a desface fiecare dintre aceste două opțiuni înainte de a le interpola, să aruncăm o privire la modul în care am putea face ambele lucruri dintr-o singură dată folosind interpolarea personalizată. Vom începe prin extinderea String.StringInterpolation
cu un nou appendInterpolation
suprasarcină care acceptă orice valoare opțională:
extension String.StringInterpolation { mutating func appendInterpolation<T>(unwrapping optional: T?) { let string = optional.map { "\($0)" } ?? "" appendLiteral(string) }}
unwrapping:
eticheta parametrilor de mai sus este importantă, deoarece este ceea ce vom folosi pentru a spune compilatorul pentru a utiliza această metodă specifică de interpolare — ca aceasta:
func save(_ text: String, prefix: String?, suffix: String?) { let text = "\(unwrapping: prefix)\(text)\(unwrapping: suffix)" textStorage.store(text)}
deși este doar zahăr sintactic, cele de mai sus arată foarte bine! Cu toate acestea, asta abia zgârie suprafața a ceea ce pot face metodele de interpolare a șirurilor personalizate. Ele pot fi atât generice, cât și non-generice, pot accepta orice număr de argumente, pot folosi valori implicite și aproape orice altceva pot face metodele „normale”.
Iată un alt exemplu în care activăm metoda noastră de conversie a unei adrese URL într-o legătură HTML înainte de a fi folosită și în contextul interpolării șirului:
extension String.StringInterpolation { mutating func appendInterpolation(linkTo url: URL, _ title: String) { let string = url.html(withTitle: title) appendLiteral(string) }}
cu cele de mai sus în loc, acum putem genera cu ușurință linkuri HTML dintr-o adresă URL ca aceasta:
webView.loadHTMLString( "If you're not redirected, \(linkTo: url, "tap here").", baseURL: nil)
lucrul interesant despre interpolarea șirurilor personalizate este modul în care compilatorul ia fiecare dintre metodele noastre appendInterpolation
și le traduce în API — uri de interpolare corespunzătoare-oferindu-ne un control complet asupra a ceea ce va arăta site-ul de apel, de exemplu prin eliminarea etichetelor parametrilor externi, așa cum am făcut pentru title
mai sus.
vom continua să căutăm mai multe modalități de utilizare a interpolării șirurilor personalizate, de exemplu cu șiruri atribuite și alte tipuri de metadate text, în articolele viitoare.
sprijiniți Swift de Sundell verificând acest sponsor:
Dezvoltator esențial: Alăturați-vă unui curs online gratuit pentru dezvoltatorii iOS care doresc să devină Dezvoltatori seniori cu centură neagră — adică: atingeți un nivel expert de abilități practice și deveniți parte a celor mai bine plătiți dezvoltatori din lume.
concluzie
în timp ce unele dintre capabilitățile literale mai avansate ale Swift sunt utile doar în situații foarte specifice, cum ar fi cele din acest articol, este bine să le aveți disponibile atunci când este necesar — mai ales că este posibil să le evitați complet și să folosiți doar șiruri"the old-fashioned way"
.
string literals este un alt domeniu în care designul orientat spre protocol al Swift strălucește cu adevărat. Delegând o mare parte din modul în care literalii sunt interpretați și manipulați către implementatorii protocoalelor, mai degrabă decât codificând greu acele comportamente în compilatorul în sine, noi, ca dezvoltatori terți, suntem capabili să personalizăm puternic modul în care literalii sunt manipulați-păstrând în același timp valorile implicite cât mai simple.