Swiftの文字列リテラル
インラインリテラルを使用して、文字列や整数などの基本的な値を表現できることは、ほとんどのプログラミング言語で不可欠な機能です。 しかし、他の多くの言語はコンパイラに組み込まれた特定のリテラルをサポートしていますが、Swiftは、独自の型システムを使用して、プロトコルを介して様々なリテラルをどのように処理すべきかを定義する、はるかに動的なアプローチを採用しています。
今週は、特に文字列リテラルに焦点を当て、それらが使用できる多くの異なる方法と、Swiftの高度なプロトコル指向の設計を通じて、リテラルの解釈方
Essential Developer:ブラックベルトシニア開発者になりたいiOS開発者のための無料のオンラインクラッシュコースに参加-それは: 専門家レベルの実践的なスキルを達成し、世界で最も高給の開発者の一部になります。
基本
他の多くの言語と同様に、Swift文字列は引用符で囲まれたリテラルで表現され、特殊なシーケンス(改行など)、エスケープされた文字、および内挿:
let string = "\(user.name) says \"Hi!\"\nWould you like to reply?"// John says "Hi!"// Would you like to reply?
上記で使用されている機能はすでに多くの柔軟性を提供していますが、大部分のユースケースでは十分な可能性が最も高いですが、より強力なリテラル表現方法が便利になる状況があります。 複数行のテキストを含む文字列を定義する必要がある場合から始めて、それらのいくつかを見てみましょう。
複数行リテラル
標準の文字列リテラルは\n
を使用して複数行に分割できますが、それは必ずしも実用的ではありません。
ありがたいことに、Swift4以降では、単一の引用符ではなく三つの引用符を使用して複数行の文字列リテラルを定義することもできます。 たとえば、ここでは、コマンドラインで呼び出すときにユーザーが引数を渡さなかった場合に備えて、その機能を使用してSwiftスクリプトのヘルプテキストを:
// 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)}
上記では、複数行の文字列リテラルが、末尾の引用符のセットを基準にしてテキストのインデントを保持するという事実を利 また、3つの引用符のセットで定義されているため、リテラルの境界があいまいになる可能性がはるかに低いため、エスケープされていない引用符をより自由に使用することができます。
上記の二つの特性の両方が、複数行リテラルをインラインHTMLを定義するための素晴らしいツールにしています。
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> """ }}
上記のテクニックは、文字列ベースのテストデータを定義するときにも本当に便利です。 たとえば、アプリの設定をXMLとしてエクスポート可能にする必要があり、その機能を検証するテストを作成するとします。 検証したいXMLを別のファイルで定義するのではなく、複数行の文字列リテラルを使用してテストにインライン化することができます。
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> """) }}
テストデータをインラインで定義する利点は、上記のように、テストコードと期待される出力が互いに隣に配置されるため、テストを書くときに行われたエラーをすばやく見つけることがはるかに簡単になることです。 ただし、テストデータの長さが数行を超える場合や、同じデータを複数の場所で使用する必要がある場合は、独自のファイルに移動する価値があります。
Raw strings
Swift5の新機能では、raw stringsを使用すると、リテラルを生の文字列として単純に扱うことを支持して、すべての動的文字列リテラル機能(補間、\n
のような特殊文字の解釈など)を無効にすることができます。 生の文字列は、文字列リテラルをポンド記号(または子供たちが呼ぶように”ハッシュタグ”)で囲むことによって定義されます:
let rawString = #"Press "Continue" to close this dialog."#
上記で複数行リテラルを使用してテストデータを定義したのと同じように、生の文字列リテラルは、引用符や円記号などの特殊文字を含 ここでは、生の文字列リテラルを使用してJSON文字列を定義し、User
インスタンスをエンコードします。
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") }}
上記では、”Type inference-powered serialization in Swift”の型推論ベースのデコーディングAPIを使用しています。
生の文字列はデフォルトで文字列補間のような機能を無効にしますが、補間の先頭のバックスラッシュの直後に別のポンド記号を追加することで上書きする方法があります。
extension URL { func html(withTitle title: String) -> String { return #"<a href="\#(absoluteString)">\#(title)</a>"# }}
最後に、生の文字列は、特定の構文を使用して文字列を解釈するときに特に便利です。特に、その構文が正規表現のような文字列リテラル内でエスケープする必要がある文字に大きく依存している場合には特に便利です。 生の文字列を使用して正規表現を定義することにより、エスケープは必要ありません。
// 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+"#)
上記の改善があっても、特にSwiftのような型安全性の高い言語のコンテキストで使用される場合、正規表現がどれほど読みやすい(そしてデバッグする)かは疑問です。 Swiftで直接カスタム文字列解析アルゴリズムを実装するよりも、正規表現を好むかどうかにかかわらず、正規表現を使用した開発者の以前の経験に
文字列リテラルを使用して値を表現する
すべての文字列リテラルはデフォルトでString
値に変換されますが、カスタム値を表現す 「Swiftの型安全識別子」で見たように、文字列リテラルのサポートを独自の型の1つに追加することで、リテラルの使用の利便性を犠牲にすることなく、型の安全性を向上させることができます。
たとえば、Searchable
プロトコルを定義して、アプリが使用するデータベースまたは基礎となるストレージを検索するためのAPIとして機能し、Query
enumを使用して、このような検索を実行するさまざまな方法をモデル化しているとします。:
protocol Searchable { associatedtype Element func search(for query: Query) -> }enum Query { case matching(String) case notMatching(String) case matchingAny()}
上記のアプローチは、各検索をどのように実行するかについて多くの力と柔軟性を与えますが、最も一般的なユースケースは、与えられた文字列に一致する要素を検索する最も簡単なものである可能性が高く、文字列リテラルを使用してそれを行うことができれば本当にいいでしょう。良いニュースは、上記のAPIを完全にそのまま維持しながら、Query
ExpressibleByStringLiteral
に準拠させることで、それを実現できるということです:
extension Query: ExpressibleByStringLiteral { init(stringLiteral value: String) { self = .matching(value) }}
そのようにして、Query
値を手動で作成することなく、一致する検索を自由に実行できます—呼び出すAPIが実際にString
UserStorage
型が検索機能を正しく実装していることを確認するテストを実装しています:
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, ) }}
カスタム文字列リテラル式は、多くの状況で、クエリや識別子などの文字列ベースの型を扱うときに、型の安全性と利便性の間で選択する これは、最も単純なユースケースから、エッジケースをカバーし、必要に応じてより多くのパワーとカスタマイズ性を提供するまで、十分にスケールするAPI設計を
カスタム補間
Swift文字列リテラルのすべての”味”が共通していることの一つは、値を補間するためのサポートです。 私たちは常にCustomStringConvertible
に準拠することで、特定の型の補間方法をカスタマイズすることができましたが、Swift5では文字列補間エン例として、任意で接頭辞と接尾辞を適用して、指定された文字列を保存したいとしましょう。 理想的には、次のように、これらの値を単純に補間して最終的な文字列を形成したいと考えています:しかし、prefix
suffix
の両方がオプションであるため、単にその説明を使用しても探している結果は生成されず、コンパイラーは警告を表示します。
String interpolation produces a debug description for an optional value
補間する前に、これら二つのオプションのそれぞれをアンラップするオプションが常にありますが、カスタム補間を使用して両方のことを一度に行う方法を見てみましょう。 まず、オプションの値を受け入れる新しいappendInterpolation
String.StringInterpolation
を拡張することから始めます。
extension String.StringInterpolation { mutating func appendInterpolation<T>(unwrapping optional: T?) { let string = optional.map { "\($0)" } ?? "" appendLiteral(string) }}
上記のunwrapping:
パラメータラベルは重要であり、その特定の補間方法を使用するようにコンパイラに指示するために使用するものであるため、パラメータラベルは重要です。このように:
func save(_ text: String, prefix: String?, suffix: String?) { let text = "\(unwrapping: prefix)\(text)\(unwrapping: suffix)" textStorage.store(text)}
それは単なる構文的な砂糖ですが、上記は本当にきれいに見えます! しかし、それはカスタム文字列補間方法ができることの表面をほとんど傷つけません。 それらは一般的でも非一般的でもあり、任意の数の引数を受け入れ、デフォルト値を使用し、”通常の”メソッドが行うことができる他のほとんど何かを
ここでは、URLをBEFOREからHTMLリンクに変換するメソッドを有効にして、文字列補間のコンテキストでも使用できるようにする別の例を示します。
extension String.StringInterpolation { mutating func appendInterpolation(linkTo url: URL, _ title: String) { let string = url.html(withTitle: title) appendLiteral(string) }}
上記の場所を使用すると、次のようなURLからHTMLリンクを簡単に生成できるようになりました:
webView.loadHTMLString( "If you're not redirected, \(linkTo: url, "tap here").", baseURL: nil)
カスタム文字列補間についてのクールなことは、コンパイラがそれぞれのappendInterpolation
メソッドを取り、それらを対応する補間Apiに変換する方法であり、上記のtitle
のように外部パラメータラベルを削除するなど、呼び出しサイトがどのように見えるかを完全に制御できるようにすることです。
今後の記事では、属性付き文字列やその他の種類のテキストメタデータなど、カスタム文字列補間を使用する方法をさらに検討していきます。
このスポンサーをチェックアウトすることにより、サンデルによってスウィフトをサポートしています。
Essential Developer:ブラックベルトシニアデベロッパになりたいiOS開発者のための無料のオンラインクラッシュコースに参加-それは:実践的なスキルの専門家レベルを達成し、世界で最高賃金の開発者の一部になります。
結論
Swiftのより高度な文字列リテラル機能のいくつかは、この記事のような非常に特定の状況でのみ有用ですが、必要なときにそれらを利用できるようになっているのはうれしいことです—特に、それらを完全に避けて文字列"the old-fashioned way"
を使用することができるからです。
文字列リテラルは、Swiftのプロトコル指向のデザインが本当に輝く別の領域です。 コンパイラ自体でこれらの動作をハードコーディングするのではなく、リテラルの解釈と処理の多くをプロトコルの実装者に委任することにより、サードパー