【Swift】String formatで文字列を自在に整形する方法


【Swift】String formatで文字列を自在に整形する方法:詳細解説

第1章: はじめに – なぜ文字列整形が必要なのか

プログラミングにおいて、様々なデータを人間が理解しやすい形で表示することは不可欠です。数値、日付、時刻、テキストなどの情報を、単にそのまま出力するだけでなく、特定の規則に従って整え、視覚的に分かりやすく加工することを「文字列整形」(String Formatting)と呼びます。

例えば、以下のような場面で文字列整形が必要になります。

  • 数値の表示: 小数点以下の桁数を揃える、通貨記号を付ける、桁区切りを入れる、符号を明確にする、固定幅で表示するなど。
  • 日付・時刻の表示: 特定の形式(例: YYYY/MM/DD HH:mm:ssMM月DD日(曜日))で表示する。
  • テーブル形式の出力: データをカラムごとに整列させ、固定幅で表示する。
  • ログ出力: タイムスタンプやログレベルなどを一定のフォーマットで出力する。
  • 進捗表示: パーセンテージや割合を整形して表示する(例: 50.0%, [#####-----])。
  • UI表示: ラベルやテキストフィールドに表示する情報を、ユーザーにとって見やすい形式に整える。

Swiftには、これらの文字列整形を実現するための複数の機能が用意されています。この記事では、特にC言語のprintfライクなフォーマット指定子を用いるString(format:)メソッドに焦点を当てて詳細に解説しつつ、現代的なSwiftで推奨される文字列補間(String Interpolation)や、より高度な整形のためのFormatterオブジェクトについても触れ、それぞれの特徴と使い分けについて深掘りしていきます。

歴史的に、Objective-CではNSString(format:)というメソッドが広く使われていました。Swiftにもこの機能が引き継がれており、Cスタイルの強力で柔軟なフォーマット指定子を利用できます。しかし、Swiftは文字列補間という、より直感的でタイプセーフな新しい方法も提供しています。さらに、Swift 5.5以降では文字列補間にformatted()メソッドを組み合わせることで、Cスタイルのフォーマット指定子に匹敵するか、それ以上の柔軟かつSwiftらしいフォーマットが可能になりました。

この記事を読むことで、あなたはSwiftで利用可能な様々な文字列整形の手法を理解し、それぞれの状況に応じて最適な方法を選択し、自在に文字列を整形できるようになるでしょう。

第2章: CスタイルのString(format:) – 基本を理解する

SwiftのString構造体には、C言語のprintf関数に似た機能を提供するイニシャライザinit(format: _ arguments: CVarArg...)(または単にString(format:)と呼ばれることが多い)があります。このメソッドは、フォーマット文字列内の「フォーマット指定子」を、対応する引数の値で置き換えることで整形を行います。

String(format: String, arguments: CVarArg...) の構文

swift
let formattedString = String(format: "フォーマット文字列", 引数1, 引数2, ...)

  • format: フォーマット指定子を含む文字列リテラルまたはString変数です。
  • arguments: フォーマット指定子に対応する値のリストです。これらの値はCVarArgプロトコルに準拠している必要があります。基本的なSwiftの数値型、StringCharacterなどはCVarArgに準拠しています。

フォーマット指定子の構造

フォーマット指定子は常に%で始まり、その後に様々なオプション指定子が続き、最後に「型指定子」が来ます。一般的な構造は以下のようになります。

%[フラグ][幅][.精度][長さ修飾子]型指定子

例えば、%dは整数を表示するための最も基本的な指定子です。%fは浮動小数点数を表示します。%sは文字列を表示します。

まずは、最も基本的な「型指定子」から見ていきましょう。

基本的な型指定子

フォーマット指定子の最後に置かれる型指定子は、対応する引数がどの型の値であるかを指示し、その値をどのように文字列に変換するかを決定します。

以下に主要な型指定子を示します。

  • %d, %i: 符号付き整数を10進数で表示します。
    swift
    let age = 30
    let formattedAge = String(format: "年齢: %d歳", age) // "年齢: 30歳"
    print(formattedAge)

    %d%iはほとんど同じですが、歴史的な経緯で存在します。通常は%dを使用すれば十分です。

  • %u: 符号なし整数を10進数で表示します。
    swift
    let count: UInt = 150
    let formattedCount = String(format: "アイテム数: %u", count) // "アイテム数: 150"
    print(formattedCount)

    UIntなどの符号なし整数型に使います。

  • %o: 符号なし整数を8進数で表示します。
    swift
    let value = 255 // 10進数
    let octal = String(format: "8進数: %o", value) // "8進数: 377"
    print(octal)

  • %x, %X: 符号なし整数を16進数で表示します。%xは小文字、%Xは大文字の16進数を使います。
    swift
    let color = 255 // 10進数
    let hexLower = String(format: "16進数 (小文字): %x", color) // "16進数 (小文字): ff"
    let hexUpper = String(format: "16進数 (大文字): %X", color) // "16進数 (大文字): FF"
    print(hexLower)
    print(hexUpper)

  • %f: 浮動小数点数を10進数(固定小数点)で表示します。デフォルトでは小数点以下6桁が表示されます。
    swift
    let pi = 3.1415926535
    let formattedPi = String(format: "円周率: %f", pi) // "円周率: 3.141593" (小数点以下6桁に丸められる)
    print(formattedPi)

  • %e, %E: 浮動小数点数を指数表記で表示します。%eは小文字のe%Eは大文字のEを使います。
    swift
    let largeNumber = 12345000000.0
    let expLower = String(format: "指数表記 (小文字): %e", largeNumber) // "指数表記 (小文字): 1.234500e+10"
    let expUpper = String(format: "指数表記 (大文字): %E", largeNumber) // "指数表記 (大文字): 1.234500E+10"
    print(expLower)
    print(expUpper)

  • %g, %G: 浮動小数点数を%fまたは%e%Gの場合は%E)のうち、より短くなる方で表示します。末尾の不要なゼロは削除されます。
    swift
    let val1 = 123.4500
    let val2 = 0.0000123
    let g1 = String(format: "%g", val1) // "123.45"
    let g2 = String(format: "%g", val2) // "1.23e-05"
    print(g1)
    print(g2)

  • %s: 文字列(Cスタイルのヌル終端文字列またはSwiftのString)を表示します。
    swift
    let name = "Alice"
    let message = String(format: "こんにちは、%sさん!", name) // "こんにちは、Aliceさん!"
    print(message)

    SwiftのStringを渡した場合、内部的にC文字列に変換されて扱われます。

  • %c: 単一の文字(CスタイルのcharまたはSwiftのCharacter)を表示します。
    swift
    let initial: Character = "S"
    let greeting = String(format: "名前の頭文字は %c です。", initial) // "名前の頭文字は S です。"
    print(greeting)

  • %p: ポインタアドレスを表示します。主にデバッグで使用されます。
    swift
    var number = 100
    let pointerString = String(format: "変数のアドレス: %p", &number) // 例: "変数のアドレス: 0x7ffee0a0d9a8"
    print(pointerString)

  • %%: リテラルなパーセント記号 % を表示します。フォーマット指定子としてではなく、単に%という文字を表示したい場合に使います。
    swift
    let percentage = 75
    let progress = String(format: "進捗率: %d%%", percentage) // "進捗率: 75%"
    print(progress)

これらの基本的な型指定子を組み合わせることで、様々なデータを文字列に変換できます。しかし、より細かな表示形式(桁数、詰め物、アライメントなど)を制御するためには、次に説明する「フラグ」「幅」「精度」といったオプション指定子が必要になります。

第3章: String(format:) – フラグ、幅、精度の詳細

String(format:)の強力さは、型指定子の前につけることができる「フラグ」「幅」「精度」といったオプションにあります。これらを組み合わせることで、文字列の表示形式を詳細に制御できます。

構造を再掲します:%[フラグ][幅][.精度][長さ修飾子]型指定子

フラグ

フラグは、表示形式に関する追加の情報を指定します。複数のフラグを組み合わせることも可能です。

  • - (マイナス): 結果をフィールドの左端に揃え、右側にスペースで埋めます。デフォルトは右揃えです。
    swift
    let name = "Bob"
    // 幅指定と組み合わせて使用
    let leftAligned = String(format: "|%-10s|", name) // "|Bob |"
    let rightAligned = String(format: "|%10s|", name) // "| Bob|" (デフォルト)
    print(leftAligned)
    print(rightAligned)

  • + (プラス): 符号付き数値に対して、正の値には+、負の値には-を表示します。デフォルトでは負の値にのみ-が表示されます。
    swift
    let pos = 100
    let neg = -50
    let posSigned = String(format: "%+d", pos) // "+100"
    let negSigned = String(format: "%+d", neg) // "-50"
    print(posSigned)
    print(negSigned)

  • (スペース): 符号付き数値に対して、正の値にはスペース、負の値には-を表示します。+フラグと同時に使用された場合は無視されます(+フラグが優先)。
    swift
    let pos = 100
    let neg = -50
    let posSpaced = String(format: "% d", pos) // " 100"
    let negSpaced = String(format: "% d", neg) // "-50"
    print(posSpaced)
    print(negSpaced)

    これは、正と負の数を同じ幅で表示したい場合に便利です(符号またはスペースで1文字分を占めるため)。

  • # (シャープ): 「代替形式」を指定します。型指定子によって意味が変わります。

    • %o: 先頭に0を付けます。
    • %x, %X: 先頭に0xまたは0Xを付けます。
    • %f, %e, %E, %g, %G: 結果に常に小数点を含めます。%g, %Gの場合は、末尾の不要なゼロも削除されません。
      swift
      let number = 255
      let floatVal = 123.0
      let octalAlt = String(format: "%#o", number) // "0377"
      let hexAlt = String(format: "%#x", number) // "0xff"
      let floatAlt = String(format: "%#f", floatVal) // "123.000000" (通常は "123" と表示されるが、#で小数点が付く)
      print(octalAlt)
      print(hexAlt)
      print(floatAlt)
  • 0 (ゼロ): フィールドをゼロで埋めます(数値型の場合)。-フラグが指定されている場合は無視されます(ゼロ埋めは右揃えの場合に意味があるため)。精度指定がされている場合も無視されます。
    swift
    let value = 42
    let zeroPadded = String(format: "%05d", value) // "00042" (幅5でゼロ埋め)
    let spacePadded = String(format: "%5d", value) // " 42" (幅5でスペース埋め - デフォルト)
    print(zeroPadded)
    print(spacePadded)

幅指定

幅指定は、出力される文字列の最小フィールド幅を指定します。結果の文字列が指定された幅より短い場合、フラグ(-または0)に応じてスペースまたはゼロで埋められます。指定された幅より長い場合は、切り捨てられずにそのまま表示されます。

幅は10進数で指定します。

“`swift
let short = “Hi”
let long = “HelloWorld”

// 幅5で右詰め(デフォルト)
let paddedShort = String(format: “|%5s|”, short) // “| Hi|”
let paddedLong = String(format: “|%5s|”, long) // “|HelloWorld|” (幅より長くても切り捨てられない)
print(paddedShort)
print(paddedLong)

// 幅5で左詰め(-フラグ)
let leftPaddedShort = String(format: “|%-5s|”, short) // “|Hi |”
print(leftPaddedShort)

// 数値の幅指定(デフォルトは右詰め、スペース埋め)
let num = 123
let numPadded = String(format: “|%6d|”, num) // “| 123|”
// 数値の幅指定(ゼロ埋め)
let numZeroPadded = String(format: “|%06d|”, num) // “|000123|”
print(numPadded)
print(numZeroPadded)
“`

アスタリスク * を使った動的な幅指定

幅は固定の数値リテラルだけでなく、アスタリスク*を使って動的に指定することも可能です。この場合、幅はフォーマット指定子に対応する引数の 前の 引数として渡す必要があります。

“`swift
let name = “Charlie”
let width = 15
let formattedName = String(format: “|%*s|”, width, name) // “| Charlie|”
print(formattedName)

let number = 999
let numWidth = 10
let numFormatted = String(format: “|%*d|”, numWidth, number) // “| 999|”
print(numFormatted)
“`
アスタリスクを使うことで、実行時に計算された幅に応じて文字列を整形できます。

精度指定

精度指定はピリオド.の後に10進数を続けて指定します。これも型指定子によって意味が変わります。

  • 浮動小数点数 (%f, %e, %E, %g, %G): ピリオドに続く数値は、小数点以下の桁数を指定します。デフォルトは6桁です。%g, %Gの場合は、有効桁数を指定します。
    “`swift
    let pi = 3.1415926535
    let precisePi = String(format: “%.2f”, pi) // “3.14” (小数点以下2桁に丸められる)
    let morePrecisePi = String(format: “%.8f”, pi) // “3.14159265” (小数点以下8桁)
    print(precisePi)
    print(morePrecisePi)

    // %g, %G の場合の精度指定(有効桁数)
    let valueG = 123.456789
    let gPrecise = String(format: “%.4g”, valueG) // “123.5” (有効桁数4桁に丸められる)
    print(gPrecise)
    “`

  • 文字列 (%s): ピリオドに続く数値は、表示する文字の最大数を指定します。文字列が指定された精度より長い場合、その長さで切り捨てられます。
    swift
    let longString = "This is a long string."
    let truncatedString = String(format: "%.10s", longString) // "This is a " (最初の10文字だけ表示)
    print(truncatedString)

    これは、一定の長さに文字列を収めたい場合に便利です。幅指定と組み合わせることもよくあります。

    swift
    let str = "Hello"
    // 幅10で、最初の3文字を表示(左詰め)
    let example = String(format: "|%-10.3s|", str) // "|Hel |"
    print(example)

  • 整数 (%d, %i, %u, %o, %x, %X): ピリオドに続く数値は、表示する最小桁数を指定します。必要に応じて先頭にゼロが埋められます。幅指定がされている場合は、幅指定の方が優先されます。
    swift
    let num = 42
    // 最小5桁で表示(ゼロ埋め)
    let zeroPaddedNum = String(format: "%.5d", num) // "00042"
    print(zeroPaddedNum)

    これは、0フラグと似ていますが、精度指定の場合はフィールド全体ではなく数値部分のみに適用され、-フラグが指定されていても有効です。ただし、通常、数値のゼロ埋めには0フラグと幅指定(例: %05d)を使う方が一般的です。

アスタリスク * を使った動的な精度指定

幅指定と同様に、精度もアスタリスク*を使って動的に指定できます。この場合、精度は対応する引数の 前、かつ幅引数の後 に引数として渡す必要があります。

“`swift
let pi = 3.1415926535
let precision = 3
let dynamicPrecision = String(format: “%.*f”, precision, pi) // “3.142”
print(dynamicPrecision)

let longString = “This is a long string.”
let length = 10
let dynamicTruncation = String(format: “%.s”, length, longString) // “This is a ”
print(dynamicTruncation)
幅と精度を両方動的に指定する場合は、引数の順番は「幅、精度、値」となります。swift
let text = “Test”
let dynamicWidth = 10
let dynamicPrecision = 3
// 幅、精度、値 の順に引数を渡す
let dynamicWidthAndPrecision = String(format: “|%
.*s|”, dynamicWidth, dynamicPrecision, text) // “|Tes |”
print(dynamicWidthAndPrecision)
“`

長さ修飾子 (Length Modifiers)

長さ修飾子は、整数型の引数のサイズを指定しますが、Swiftでは通常、これらの修飾子を明示的に指定する必要はありません。Swiftの型(Int, Int32, Int64など)はCの型に対応しており、String(format:)は引数の型を推論して適切に処理するためです。しかし、C言語との相互運用や、古いCスタイルのフォーマット指定子を扱う際には知っておくと役立ちます。

一般的な長さ修飾子:
* l: long (例: %ldIntまたはInt64に相当)
* ll: long long (例: %lldInt64に相当)
* h: short (例: %hdInt16に相当)
* hh: signed char (例: %hhdInt8に相当)
* L: long double (例: %Lf – Swiftでは通常DoubleまたはFloat80が使われる)

Swiftでは、例えばInt64の値を%dでフォーマットしようとすると警告が出る場合がありますが、通常は%lldを使うことで対応できます。しかし多くの場合、SwiftのIntはターゲット環境のネイティブなワードサイズ(32ビットまたは64ビット)に対応するため、単に%dを使うだけで適切に処理されます。明示的な長さ修飾子が必要になるのは、特定のサイズの整数型を確実に扱いたい場合に限られます。

複数の引数を持つフォーマット

もちろん、フォーマット文字列には複数のフォーマット指定子を含めることができ、対応する引数を順に渡します。

“`swift
let product = “Laptop”
let price = 1200.50
let quantity = 2
let totalPrice = Double(price * Double(quantity))

let receiptLine = String(format: “商品: %s, 価格: %.2f, 数量: %d, 合計: %.2f”, product, price, quantity, totalPrice)
// “商品: Laptop, 価格: 1200.50, 数量: 2, 合計: 2401.00”
print(receiptLine)
“`
引数はフォーマット指定子が現れる順序で渡す必要があります。

NSString(format:)String(format:) の違い

歴史的には、Objective-CのNSStringクラスがformat:メソッドを提供していました。SwiftのString構造体は値型ですが、Foundationフレームワークをインポートすると、StringNSStringの多くの機能にアクセスできるようになります(ブリッジング)。

Swift 5より前は、Cスタイルのフォーマット機能を使う際にはNSString(format:)を使用し、その結果をSwiftのStringに変換することが一般的でした。

“`swift
// Swift 5より前 (Foundationが必要)
import Foundation

let value = 123
let formattedString = NSString(format: “Value: %d”, value) as String
print(formattedString)
“`

Swift 5以降では、String自身がinit(format: _ arguments: CVarArg...)イニシャライザを持つようになり、Foundationをインポートしなくても、またNSStringを経由しなくても直接Cスタイルのフォーマットが使えるようになりました。

swift
// Swift 5以降
let value = 123
let formattedString = String(format: "Value: %d", value)
print(formattedString)

特別な理由がない限り、Swift 5以降ではString(format:)を直接使うことを推奨します。これはよりSwiftらしい記述であり、NSStringへのブリッジングに伴うオーバーヘッドもありません。

Swiftの数値型とCフォーマット指定子の対応関係

Swiftの数値型はCの数値型に対応していますが、いくつかの注意点があります。

Swift型 対応するC型 主なフォーマット指定子 注意点
Int longまたはlong long (環境依存) %d, %i, %o, %x, %X 通常%dで問題ないが、環境によっては%ld%lldがより正確な場合も。
Int8 signed char %hhd %dでも処理されるが、明示的に%hhdを使うのが確実。
Int16 short %hd %dでも処理されるが、明示的に%hdを使うのが確実。
Int32 int %d, %i Cのintに直接対応。
Int64 long long %lld 64ビット整数には%lldを推奨。
UInt unsigned longまたはunsigned long long %u, %o, %x, %X 通常%uで問題ないが、環境によっては%lu%lluがより正確な場合も。
UInt8 unsigned char %hhu 明示的に%hhuを使うのが確実。
UInt16 unsigned short %hu 明示的に%huを使うのが確実。
UInt32 unsigned int %u Cのunsigned intに直接対応。
UInt64 unsigned long long %llu 64ビット符号なし整数には%lluを推奨。
Float float %f, %e, %E, %g, %G %fなどで問題なく扱える。
Double double %f, %e, %E, %g, %G %fなどで問題なく扱える。
Float80 long double %Lf Float80を使う場合は%Lfが対応するが、利用はまれ。
Bool %d, %i 整数値 (0または1) として扱われる。
Character char %c CCharとして扱われる。
String const char* %s SwiftのStringからC文字列への変換が行われる。

多くの場合は、単に%d%f%sなどを使ってもSwiftの型からCの型への適切な変換が行われるため問題ありません。しかし、特に異なるプラットフォームやアーキテクチャで動作する可能性のあるコードで、特定のサイズの整数を確実に扱いたい場合は、長さ修飾子を明示的に使用することを検討しても良いでしょう。

CVarArg プロトコル

String(format:)の可変長引数はCVarArgプロトコルに準拠している必要があります。このプロトコルは、Cの可変長引数リストに渡すことができる型であることを示します。Swiftの基本的な数値型、StringCharacterなどは標準でCVarArgに準拠しています。自作の型をString(format:)でフォーマットしたい場合は、その型がCVarArgに準拠している必要がありますが、通常はその必要に迫られることは少ないでしょう。

第4章: よりSwiftらしい方法 – 文字列補間 (String Interpolation)

Swiftでは、CスタイルのString(format:)よりも推奨される、より直感的でタイプセーフな文字列整形方法があります。それが文字列補間(String Interpolation)です。

文字列補間は、文字列リテラル内に\(...)という構文を用いて、変数、定数、リテラル、または任意の式の結果を埋め込む機能です。

文字列補間の基本的な使い方

“`swift
let name = “Swift”
let year = 2014
let version = 5.8

// 基本的な補間
let greeting = “Hello, (name)!” // “Hello, Swift!”
print(greeting)

// 複数の値を補間
let info = “(name)は(year)年に発表され、現在はバージョン(version)です。”
// “Swiftは2014年に発表され、現在はバージョン5.8です。”
print(info)

// 式の評価結果を補間
let a = 10
let b = 20
let sum = “a + b の合計は (a + b) です。” // “a + b の合計は 30 です。”
print(sum)
``
文字列補間は、
String(format:)`と比較して以下の利点があります。

  • 可読性: フォーマット文字列と埋め込む値がインラインで記述されるため、コードが読みやすいです。
  • タイプセーフティ: コンパイラが埋め込まれる値の型をチェックします。String(format:)のように、指定子と引数の型が一致しないことによる実行時エラーを防げます。
  • 柔軟性: 任意の式やオブジェクトを埋め込むことができます。埋め込まれる値は自動的に文字列に変換されます。

カスタム型の補間

Swiftの文字列補間は、埋め込まれる値のdescriptionまたはdebugDescriptionプロパティ(CustomStringConvertibleまたはCustomDebugStringConvertibleプロトコルが要求する)を利用して文字列化を行います。

  • CustomStringConvertible: オブジェクトの標準的な文字列表現を提供します。ほとんどのSwift標準ライブラリの型はこれに準拠しています。print()関数や文字列補間でデフォルトで使用されます。
  • CustomDebugStringConvertible: オブジェクトのデバッグ用文字列表現を提供します。デバッグ出力(例: debugPrint()) や文字列補間内の\(debug: ...)構文で使用されます。

自作のクラスや構造体を文字列補間で適切に表示したい場合は、CustomStringConvertibleプロトコルに準拠させてdescriptionプロパティを実装するのが一般的です。

“`swift
struct Point: CustomStringConvertible {
let x: Int
let y: Int

// CustomStringConvertible の要件
var description: String {
    return "(\(x), \(y))" // 補間内で再び文字列補間を使用
}

}

let origin = Point(x: 0, y: 0)
let message = “座標: (origin)” // “座標: (0, 0)” – Pointのdescriptionが使われる
print(message)
“`

値のフォーマット制御 – formatted() メソッド (Swift 5.5+)

文字列補間は非常に便利ですが、CスタイルのString(format:)のような詳細なフォーマット制御(小数点以下の桁数、ゼロ埋め、幅指定など)を直接行う機能は、登場当初は限定的でした。しかし、Swift 5.5で導入されたformatted()メソッドにより、文字列補間の内部や単体で、強力かつSwiftらしいフォーマットが可能になりました。

formatted()メソッドは、数値や日付などの値を特定の形式で文字列化するために使用します。様々なフォーマットスタイルやオプションを指定できます。

数値のフォーマット

  • デフォルトフォーマット: 単にformatted()を呼び出すと、デフォルトの数値フォーマット(通常は小数点以下の桁数制限なし)になります。
    swift
    let number = 123456.789
    let formattedNumber = number.formatted() // "123,456.789" (ロケール依存で桁区切りが付く)
    print(formattedNumber)

  • 小数点以下の桁数、有効桁数: .numberスタイルと.precisionオプションを使用します。
    swift
    let pi = 3.1415926535
    // 小数点以下2桁
    let fixedPrecision = pi.formatted(.number.precision(.fractionLength(2))) // "3.14"
    // 有効桁数4桁
    let significantPrecision = pi.formatted(.number.precision(.significantDigits(4))) // "3.142"
    print(fixedPrecision)
    print(significantPrecision)

  • 桁区切り: .numberスタイルで.groupingオプションを使用します。.automaticが一般的です。
    swift
    let largeNum = 1234567890
    let groupedNum = largeNum.formatted(.number.grouping(.automatic)) // "1,234,567,890"
    print(groupedNum)

  • パーセンテージ: .percentスタイルを使用します。
    swift
    let ratio = 0.75
    let percentage = ratio.formatted(.percent) // "75%"
    print(percentage)

  • 通貨: .currency(code:)または.currency(symbol:)スタイルを使用します。ロケールに応じて適切にフォーマットされます。
    swift
    let amount = 99.99
    // ISO 4217 通貨コードを使用
    let usd = amount.formatted(.currency(code: "USD")) // "$99.99" (ロケールによる)
    let jpy = amount.formatted(.currency(code: "JPY")) // "¥99" (ロケールによる - 日本円は通常小数点以下を表示しない)
    print(usd)
    print(jpy)

  • 幅とアライメント、パディング: .numberスタイルと.width, .leadingPaddingオプションを使用します。
    swift
    let value = 42
    // 幅5、右寄せ、スペース埋め (デフォルト)
    let paddedValue = value.formatted(.number.width(5)) // " 42"
    // 幅5、ゼロ埋め (先頭パディングに0を指定)
    let zeroPaddedValue = value.formatted(.number.width(5).leadingPadding(to: "0")) // "00042"
    // 幅5、左寄せ、スペース埋め (トレイルパディングにスペースを指定)
    let leftPaddedValue = value.formatted(.number.width(5).trailingPadding(to: " ")) // "42 "
    print("|\(paddedValue)|")
    print("|\(zeroPaddedValue)|")
    print("|\(leftPaddedValue)|")

    文字列の幅指定とアライメントには、直接formatted()メソッドを使うことは少なく、後述する方法が使われることが多いですが、数値に関してはformatted()が非常に強力です。

日付・時刻のフォーマット

Date型の値に対してもformatted()メソッドが使えます。

  • デフォルトフォーマット: ロケールに基づいて日付と時刻を表示します。
    swift
    let now = Date() // 現在の日時
    let defaultFormatted = now.formatted() // 例: "2023/10/27 12:34" (ロケール依存)
    print(defaultFormatted)

  • スタイル指定: .date, .timeオプションでスタイルを指定します。
    swift
    // 日付のみ、短いスタイル
    let shortDate = now.formatted(date: .short, time: .omitted) // 例: "23/10/27"
    // 時刻のみ、長いスタイル
    let longTime = now.formatted(date: .omitted, time: .long) // 例: "12:34:56 JST"
    // 日付と時刻、両方ミディアムスタイル
    let mediumDateTime = now.formatted(date: .medium, time: .medium) // 例: "2023/10/27 12:34:56"
    print(shortDate)
    print(longTime)
    print(mediumDateTime)

  • カスタムフォーマット: dateFormatオプションや、より柔軟な.dateTimeスタイルを使用します。
    swift
    // カスタムパターン (DateFormatterのパターン記法を使用)
    let customPattern = now.formatted(.dateTime.dateFormat("yyyy年MM月dd日(EEEEE) HH:mm")) // 例: "2023年10月27日(金) 12:34"
    print(customPattern)

    formatted()メソッドは、DateFormatterを直接使うよりも簡潔に記述できる場合が多いです。ロケール指定も引数で行えます (.formatted(locale: Locale(identifier: "en_US"), ...)).

その他の型のフォーマット

formatted()メソッドは、Measurement(単位付き数値)やPersonNameComponentsなど、他の多くのFoundationフレームワークの型に対しても提供されています。

formatted()メソッドは、CスタイルのString(format:)が持っていた数値や日付の整形能力を、よりSwiftらしい構文とタイプセーフティで実現します。特に国際化(i18n)においては、ロケールを考慮した適切なフォーマットを簡単に実現できるため非常に有用です。

文字列補間の内部でformatted()を呼び出すことも可能です。

“`swift
let price = 150.75
let discount = 0.15
let discountedPrice = price * (1 – discount)

// 補間と formatted() の組み合わせ
let summary = “””
元価格: (price.formatted(.currency(code: “EUR”)))
割引率: (discount.formatted(.percent))
割引後価格: (discountedPrice.formatted(.currency(code: “EUR”)))
“””
print(summary)
“`

第5章: String(format:) と 文字列補間 (String Interpolation) の比較と使い分け

SwiftにはString(format:)と文字列補間という主要な文字列整形方法があります。それぞれに長所と短所があり、状況に応じて使い分けるのが賢明です。

String(format:) のメリット

  1. C言語からの移植性: CやObjective-Cのコードベースからの移植が容易です。既存のフォーマット文字列をそのまま流用できます。
  2. コンパクトな数値・文字列整形: 数値のゼロ埋め、固定小数点表示の桁数制御、文字列の切り捨てなど、特定の低レベルな整形がフォーマット文字列内で簡潔に記述できます(例: %05d, %.2f, %.10s)。特に固定幅の表形式出力などに向いています。
  3. 動的なフォーマット文字列: フォーマット文字列自体を実行時に動的に生成する必要がある場合に利用できます。ただし、セキュリティ上のリスク(後述)に注意が必要です。

String(format:) のデメリット

  1. タイプセーフティの欠如: フォーマット指定子と引数の型が一致しない場合、コンパイル時にはエラーにならず、実行時エラー(クラッシュ)の原因となる可能性があります。また、引数の数や順番がフォーマット指定子と一致しない場合も同様です。
  2. 可読性の低下: フォーマット文字列が複雑になると読みにくくなります。特に、フラグ、幅、精度などのオプションが多数含まれる場合、どのような結果になるか一見して分かりにくいことがあります。
  3. Swiftらしさの欠如: Swiftのモダンな機能である値型やプロトコル指向プログラミングの恩恵をあまり受けられません。
  4. 国際化(i18n)への対応: デフォルトではシステムのロケールを使用しますが、明示的にロケールを指定しないと、数値の小数点区切りや桁区切り、日付の形式などが意図しないものになる可能性があります。ロケール対応のためにはString(format: locale: ...)を使用する必要がありますが、それでもNumberFormatterDateFormatterほどの柔軟性はありません。
  5. セキュリティリスク: 外部から提供された文字列をそのままフォーマット文字列として使用すると、悪意のあるコード(例: %@%n指定子を利用したメモリ破壊など)が実行される可能性があります。信頼できないソースからの入力は、決して直接フォーマット文字列として使用してはいけません。

文字列補間 (String Interpolation) のメリット

  1. タイプセーフティ: コンパイラが式の型をチェックするため、型に関するエラーはコンパイル時に検出されます。
  2. 可読性: 埋め込む値が文字列リテラル内に直接記述されるため、コードが直感的で読みやすいです。
  3. Swiftらしい記述: Swiftの構文に自然に溶け込み、値型やプロトコル指向の恩恵を受けられます。
  4. 柔軟性: 任意の式やカスタム型のインスタンスを簡単に文字列化できます(CustomStringConvertibleなどに準拠していれば)。
  5. formatted()メソッドによる強力な整形 (Swift 5.5+): formatted()を組み合わせることで、数値の桁区切り、小数点以下の桁数、通貨、パーセンテージ、日付・時刻など、多様なフォーマットをタイプセーフかつロケール対応で行えます。

文字列補間 (String Interpolation) のデメリット

  1. Cスタイルの低レベル整形は不得意: String(format:)の持つ、フラグや幅、精度を指定するようなCスタイルのコンパクトな記法は直接使えません。特に文字列の切り捨てや、数値・文字列のゼロ埋め・スペース埋めによる固定幅表示は、formatted()を使用してもString(format:)ほど簡潔ではない場合があります。
  2. 動的なフォーマットパターン: formatted()メソッドはフォーマットスタイルやオプションをメソッド呼び出しで指定するため、実行時にフォーマットパターン自体(例: %.2f全体)を動的に変更するようなケースには不向きです(String(format:)がこのようなケースでは有利な場合がありますが、推奨されません)。

使い分けの指針

  • ほとんどの場合: 文字列補間を使用しましょう。特に、シンプルな値の埋め込み、計算結果の表示、カスタム型の表示など、複雑な低レベル整形が必要ない場面では文字列補間が最もSwiftらしく、安全で読みやすい方法です。
  • 数値、日付の標準的なフォーマット: Swift 5.5以降であれば、文字列補間とformatted()メソッドを組み合わせるのが強力です。ロケール対応も容易で、タイプセーフです。通貨、パーセンテージ、標準的な日付・時刻形式などに最適です。
  • 固定幅でのカラム表示など、厳密な低レベル整形が必要な場合: String(format:)が依然として有効な選択肢となることがあります。特に、数値のゼロ埋めや、文字列の特定の長さでの切り捨てとパディングを組み合わせて、表形式のような出力を行う場合です。ただし、この場合でも引数の型や数を間違えないように細心の注意が必要です。
  • C/Objective-Cコードからの移植: 既存のCスタイルのフォーマット文字列をそのまま使いたい場合は、String(format:)が便利です。
  • 動的なフォーマット文字列を必要とする場合 (非推奨): どうしてもフォーマット文字列自体を動的に生成・変更したい場合はString(format:)を使うことになりますが、セキュリティリスクを十分に理解し、信頼できないソースからの入力は絶対に直接フォーマット文字列に使用しないでください。

一般的に、新しいSwiftコードを書く際には、まず文字列補間とformatted()メソッドを検討し、それで実現できないような特定の低レベルな整形が必要な場合にのみString(format:)の使用を検討するのが良いアプローチです。

第6章: 実用的な文字列整形テクニックと例

これまでに学んだString(format:)、文字列補間、formatted()メソッドを使って、様々な実用的な文字列整形を実現する例を見ていきましょう。

例1: 固定幅でのカラム出力 (表形式)

複数のデータをカラムに分けて表示する場合、固定幅で整形すると視覚的に整った表になります。これはString(format:)の幅指定が非常に得意とする分野です。

“`swift
let data = [
(“Apple”, 150, 2.5),
(“Banana”, 80, 1.8),
(“Orange”, 120, 3.1)
]

print(String(format: “%-10s | %5s | %8s”, “商品名”, “数量”, “単価”))
print(String(format: “%-10s | %5s | %8s”, “———-“, “—–“, “——–“))

for item in data {
let name = item.0
let quantity = item.1
let price = item.2
// %-10s: 文字列、幅10、左詰め
// %5d: 整数、幅5、右詰め
// %8.2f: 浮動小数点数、幅8、小数点以下2桁、右詰め
print(String(format: “%-10s | %5d | %8.2f”, name, quantity, price))
}

/ 出力例:
商品名 | 数量 | 単価
———- | —– | ——–
Apple | 150 | 2.50
Banana | 80 | 1.80
Orange | 120 | 3.10
/
``
この例では、文字列は左詰め(
フラグ)、数値は右詰め(デフォルト)で、それぞれに固定幅を指定しています。浮動小数点数には小数点以下2桁表示(.2f`)も適用しています。

例2: 数値のゼロ埋め

ファイル名やIDなど、特定の桁数で数値をゼロ埋めしたい場合があります。

“`swift
let id = 42
let zeroPaddedId = String(format: “ID-%05d”, id) // “ID-00042”
print(zeroPaddedId)

let counter = 10
// Swift 5.5+ formatted() を使う場合
let formattedCounter = counter.formatted(.number.width(3).leadingPadding(to: “0”)) // “010”
print(formattedCounter)
``
数値のゼロ埋めは
String(format:)0フラグと幅指定(%0nd)が非常に簡潔です。formatted()`でも同様のことができますが、少し記述が長くなります。

例3: 小数点以下の桁数を制御した表示

浮動小数点数を表示する際に、小数点以下の桁数を指定したいことはよくあります。

“`swift
let value = 123.456789
// String(format:)
let formattedValueFormat = String(format: “値: %.2f”, value) // “値: 123.46”
print(formattedValueFormat)

// Swift 5.5+ formatted()
let formattedValueFormatted = “値: (value.formatted(.number.precision(.fractionLength(2))))” // “値: 123.46”
print(formattedValueFormatted)
``
どちらの方法でも同様の結果が得られますが、
formatted()`は小数点区切りなどのロケール対応も自動で行います。

例4: パーセンテージ表示

割合をパーセンテージで表示します。

“`swift
let ratio = 0.855

// String(format:) – 100倍して %.1f%% など
let percentageFormat = String(format: “完了率: %.1f%%”, ratio * 100) // “完了率: 85.5%”
print(percentageFormat)

// Swift 5.5+ formatted() – .percent スタイル
let percentageFormatted = “完了率: (ratio.formatted(.percent.precision(.fractionLength(1))))” // “完了率: 85.5%”
print(percentageFormatted)
``formatted(.percent)スタイルは、値を自動的に100倍して%記号を付けてくれます。小数点以下の桁数も.precisionで指定できます。パーセンテージ表示はformatted()`を使う方がタイプセーフで意図が明確になります。

例5: 通貨表示とロケール

通貨表示は、数値だけでなく通貨記号、小数点、桁区切りなどのロケール固有のルールに従う必要があります。

“`swift
let price = 199.99

// String(format:) – ロケールを指定しないとシステムロケールに依存
let priceUSD = String(format: “$%.2f”, price) // 例: “$199.99” (システムロケールがUSAの場合)
print(“価格 (format, default locale): (priceUSD)”)

// String(format: locale: …) – ロケールを指定
let usLocale = Locale(identifier: “en_US”)
let priceUSD_Locale = String(format: usLocale, “$%.2f”, price) // “$199.99”
print(“価格 (format, en_US): (priceUSD_Locale)”)

let jpLocale = Locale(identifier: “ja_JP”)
// 注意: 日本円は小数点以下を表示しないのが一般的ですが、%.2fで無理やり表示
let priceJPY_LocaleFormat = String(format: jpLocale, “¥%.0f”, price * 150) // 例: “¥29998” (小数点以下なし)
print(“価格 (format, ja_JP): (priceJPY_LocaleFormat)”)

// Swift 5.5+ formatted() – .currency スタイル (ロケール対応が組み込まれている)
let priceUSD_Formatted = price.formatted(.currency(code: “USD”)) // “$199.99” (en_USロケールなら)
print(“価格 (formatted, default locale, USD): (priceUSD_Formatted)”)

let priceJPY_Formatted = (price * 150).formatted(.currency(code: “JPY”)) // “¥29,999” (ja_JPロケールなら – 桁区切りと小数点以下なし)
print(“価格 (formatted, default locale, JPY): (priceJPY_Formatted)”)

// 特定のロケールを指定して formatted()
let priceUSD_Formatted_enUS = price.formatted(.currency(code: “USD”).locale(usLocale)) // “$199.99”
print(“価格 (formatted, en_US, USD): (priceUSD_Formatted_enUS)”)

let priceJPY_Formatted_jaJP = (price * 150).formatted(.currency(code: “JPY”).locale(jpLocale)) // “¥29,999”
print(“価格 (formatted, ja_JP, JPY): (priceJPY_Formatted_jaJP)”)
``
通貨表示のようにロケール固有のルールが多い場合は、
formatted()メソッドや後述のNumberFormatterを使うのが最も適切です。String(format: locale:)もロケールを指定できますが、通貨記号の位置や小数点以下の扱いなど、複雑なローカライズ規則への対応は限定的です。formatted()NumberFormatter`の機能をよりSwiftらしい構文で提供します。

例6: 日付・時刻のカスタマイズ表示

特定の日付・時刻形式で表示します。

“`swift
let now = Date()

// String(format:) – 直接日付を整形するのは難しい
// Cのstrftime関数に似た機能がObjective-Cにはあるが、SwiftのString.formatでは直接使えない
// 通常は DateFormatter を使う必要がある

// DateFormatter を使う方法 (Swiftの標準的な方法)
let formatter = DateFormatter()
formatter.dateFormat = “yyyy年MM月dd日(EEEEE) HH:mm:ss”
formatter.locale = Locale(identifier: “ja_JP”) // 日本語ロケールを指定

let formattedDateFormatter = formatter.string(from: now) // “2023年10月27日(金) 12:34:56”
print(“現在日時 (DateFormatter): (formattedDateFormatter)”)

// Swift 5.5+ formatted()
let formattedDateFormatted = now.formatted(.dateTime.dateFormat(“yyyy年MM月dd日(EEEEE) HH:mm:ss”).locale(jpLocale))
print(“現在日時 (formatted): (formattedDateFormatted)”)

// formatted() のスタイル指定
let dateOnly = now.formatted(date: .long, time: .omitted) // 例: “2023年10月27日” (ja_JPロケールなら)
print(“日付のみ (formatted): (dateOnly)”)
``
日付・時刻の整形は、
String(format:)の担当範囲外です。SwiftではDateFormatterまたはSwift 5.5以降のformatted()メソッドを使用するのが標準的です。formatted()DateFormatter`をラップしたような機能であり、より簡潔に書けることが多いです。

例7: 文字列のパディングと切り捨て

文字列を固定幅で表示し、短い場合はスペースで埋め、長い場合は切り捨てたい場合があります。

“`swift
let shortString = “Swift”
let longString = “String Formatting Example”
let width = 15

// String(format:)
// 幅15、左詰め、スペース埋め
let paddedShortFormat = String(format: “|%-s|”, width, shortString) // “|Swift |”
// 幅15、最初の15文字を表示、左詰め、スペース埋め
let paddedLongFormat = String(format: “|%-
.*s|”, width, width, longString) // “|String Formatti|”
print(“Short (format): (paddedShortFormat)”)
print(“Long (format): (paddedLongFormat)”)

// 文字列補間 + Stringメソッド (手動でのパディング/切り捨て)
func paddedAndTruncated(string: String, width: Int, padChar: Character = ” “, align: String.Alignment = .left) -> String {
if string.count > width {
return String(string.prefix(width))
} else if string.count < width {
let paddingCount = width – string.count
let padding = String(repeating: padChar, count: paddingCount)
switch align {
case .left: return string + padding
case .right: return padding + string
case .center:
let pre = paddingCount / 2
let post = paddingCount – pre
return String(repeating: padChar, count: pre) + string + String(repeating: padChar, count: post)
}
} else {
return string
}
}

let paddedShortManual = “| (paddedAndTruncated(string: shortString, width: width, align: .left)) |”
let paddedLongManual = “| (paddedAndTruncated(string: longString, width: width, align: .left)) |”
print(“Short (manual): (paddedShortManual)”)
print(“Long (manual): (paddedLongManual)”)

// String(format:)は、幅指定と精度指定を組み合わせることで、短い場合はパディング、長い場合は切り捨てを同時に行えるため、固定幅のカラム出力において依然として非常に便利な記法です。
// 文字列補間や formatted() だけでは、この組み合わせを簡潔に実現するのは難しい場合があります。
``
文字列のパディングや切り捨てに関しては、
String(format:)の幅と精度の組み合わせが最も直接的で簡潔な記法を提供します。文字列補間やformatted()で同様のことを実現するには、手動で文字列操作(padding(toLength:withPad:startingAt:)prefix()suffix()`など)を行う必要があります。

第7章: ロケールと国際化 (Internationalization)

アプリケーションを様々な国や地域で使用する場合、数値、日付、時刻、通貨などの表示形式をその地域の慣習に合わせる必要があります。これを国際化(i18n)と呼びます。Swiftの文字列整形機能は、ロケールを考慮に入れることができます。

Locale オブジェクト

Locale構造体は、ユーザーの言語、地域、文化的な慣習に関する情報(数値の区切り文字、通貨記号、日付の順序など)をカプセル化します。通常はLocale.currentで現在のデバイスの設定されているロケールを取得します。特定のロケールを明示的に指定したい場合は、識別子を使ってLocale(identifier: "en_US")のように作成します。

String(format: locale: ...)

String(format:)イニシャライザには、ロケールを指定できるオーバーロードがあります。

“`swift
let value = 1234.56

let usLocale = Locale(identifier: “en_US”)
let formattedUS = String(format: usLocale, “%f”, value) // “1,234.560000”

let frLocale = Locale(identifier: “fr_FR”) // フランスのロケール
let formattedFR = String(format: frLocale, “%f”, value) // “1 234,560000” (スペース区切り、カンマ小数点)

print(“US Format: (formattedUS)”)
print(“FR Format: (formattedFR)”)
``
ロケールを指定すると、数値の桁区切り文字や小数点文字がそのロケールに合わせて出力されます。通貨記号や日付形式など、より複雑なロケール対応が必要な場合は、
NumberFormatterDateFormatter、またはformatted()`メソッドがより適しています。

formatted() メソッドとロケール

前述の通り、Swift 5.5以降のformatted()メソッドはロケール対応が非常に強力です。デフォルトではLocale.currentを使用しますが、.locale()オプションで明示的にロケールを指定できます。

“`swift
let amount = 12345.67

// デフォルトロケールでの通貨表示
let defaultCurrency = amount.formatted(.currency(code: “EUR”)) // 例: “€12,345.67” (en_USロケールの場合)
print(“Default Locale Currency: (defaultCurrency)”)

// ドイツ語ロケールでの通貨表示
let deLocale = Locale(identifier: “de_DE”) // ドイツ語ロケール
let deCurrency = amount.formatted(.currency(code: “EUR”).locale(deLocale)) // “12.345,67 €” (ドイツの形式)
print(“German Locale Currency: (deCurrency)”)

// 日付のロケール対応
let now = Date()
let jpDate = now.formatted(date: .full, time: .shortened).locale(jpLocale) // 例: “2023年10月27日金曜日 12:34”
print(“Japanese Date: (jpDate)”)
``formatted()`は、それぞれのロケールにおける数値や日付、通貨の表示規則(桁区切り、小数点、通貨記号の位置、日付要素の順序など)を適切に処理してくれるため、国際化対応のアプリケーション開発において非常に有用です。

NumberFormatterDateFormatter

Foundationフレームワークが提供するNumberFormatterDateFormatterクラスは、ロケール対応を含む、より詳細で柔軟な数値・日付整形機能を提供します。これらのクラスは歴史が長く、多くのオプションを持っています。

“`swift
// NumberFormatter の例
let largeValue = 9876543.21
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal // 桁区切り
numberFormatter.locale = Locale(identifier: “es_ES”) // スペインのロケール

let formattedNumber = numberFormatter.string(from: largeValue as NSNumber) // “9.876.543,21” (スペインの形式)
print(“NumberFormatter: (formattedNumber ?? “Error”)”)

// DateFormatter の例 (前述の例と同じですが、formatted() の基盤となっています)
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .short
dateFormatter.locale = Locale(identifier: “en_GB”) // イギリス英語ロケール

let formattedDate = dateFormatter.string(from: Date()) // 例: “27 October 2023 at 12:34”
print(“DateFormatter: (formattedDate)”)
``NumberFormatterDateFormatterは非常に強力でカスタマイズ性が高いですが、オブジェクトを生成して設定する必要があるため、簡単な整形には冗長に感じられることがあります。Swift 5.5以降は、多くの場合formatted()メソッドで同等以上の機能がより簡潔な構文で実現できます。しかし、NumberFormatterの特定のスタイル(例:.spellOut- 数値をスペルアウト、.ordinal- 序数)など、formatted()`だけでは直接対応していない機能も存在します。

国際化対応のまとめ

  • 単純な値の埋め込みには、文字列補間(ロケールはほとんど関係しない)。
  • 数値の基本的な整形(桁区切り、小数点以下桁数、ゼロ埋め)でロケールを意識する必要がある場合は、String(format: locale: ...)またはformatted()
  • 通貨、パーセンテージ、日付・時刻など、複雑なロケール規則に従う整形は、formatted()が最もSwiftらしく推奨される。NumberFormatterDateFormatterも引き続き利用可能で、より詳細なカスタマイズが必要な場合に有効。
  • 固定幅出力など、Cスタイルの低レベル整形が必要な場合は、String(format:)だが、ロケール対応が必要ならString(format: locale: ...)を検討。

適切な方法を選択することで、アプリケーションの国際化対応が容易になります。

第8章: パフォーマンスとその他の考慮事項

文字列整形方法を選択する際には、パフォーマンス、タイプセーフティ、可読性、保守性などの要素も考慮に入れる必要があります。

パフォーマンス

一般的に、Swiftの文字列補間はString(format:)よりもパフォーマンスが高い傾向があります。String(format:)はCライブラリのprintf関数を呼び出すためのブリッジングや、Cの可変長引数リストの処理オーバーヘッドが伴うためです。文字列補間はSwiftランタイム内で直接処理されるため、より効率的です。

ただし、多くの場合、文字列整形はパフォーマンスがボトルネックになるような処理ではありません。特にUI表示やログ出力など、ユーザーのインタラクション頻度や出力量が限定的な場面では、パフォーマンスの違いはほとんど無視できるでしょう。数万、数十万といった非常に大量の文字列をループ内で整形する必要があるような、パフォーマンスがクリティカルな場面でのみ、この点を考慮に入れる価値があります。

Swift 5.5+ のformatted()メソッドは、内部的に最適化されたフォーマッターを使用するため、これも一般的に効率的です。

タイプセーフティ

これは既に述べた最も重要な違いの一つです。

  • String(format:): コンパイル時には型の不一致を検出できず、実行時エラーのリスクがある。
  • 文字列補間: コンパイル時に式の型チェックが行われるため、型に関するエラーを防げる。
  • formatted()メソッド: メソッドチェーンでフォーマットオプションを指定するため、コンパイラが型チェックを行い、無効なオプション指定を検出できる。

開発の安全性と効率を考えると、タイプセーフティの高い文字列補間とformatted()メソッドが明らかに優れています。

可読性と保守性

  • String(format:): シンプルな場合は良いが、フラグ、幅、精度などが組み合わさるとフォーマット文字列が読みにくくなる。引数のリストも、どの引数がどのフォーマット指定子に対応するのかを追うのが大変になる場合がある。
  • 文字列補間: 文字列リテラル内に値が直接埋め込まれるため、最も直感的で可読性が高い。
  • formatted()メソッド: チェーンでオプションを繋げていくスタイルは、慣れれば意図が分かりやすい。例えば.currency(code: "USD").precision(.fractionLength(2))のように、何を設定しているかがメソッド名から明確になる。

コードの保守性という観点でも、タイプセーフで可読性の高い文字列補間とformatted()メソッドが有利です。フォーマットの変更が必要になった場合でも、コードの意図を把握しやすいため、修正作業が容易になります。

セキュリティ

String(format:)を使用する場合、特に注意すべきセキュリティ上のリスクがあります。もし外部からの入力(ユーザーが入力した文字列など)を直接フォーマット文字列として使用した場合、攻撃者が特定のフォーマット指定子(例: %@%n)を挿入することで、アプリケーションのクラッシュやメモリ破壊を引き起こす可能性があります。

例として、Objective-Cの文脈では%n指定子が、対応する引数として渡されたポインタの位置に、そこまでに標準出力された文字数を書き込む、という脆弱性が過去に悪用されたことがあります。SwiftのString(format:)でも同様の挙動が起こりうるため、信頼できないソースからの文字列をフォーマット文字列として使うことは絶対に避けるべきです。

文字列補間にはこのようなセキュリティリスクはありません。埋め込まれるのはあくまで値であり、フォーマットの仕組み自体を操作することはできません。

したがって、セキュリティが重要なアプリケーションにおいては、可能な限り文字列補間やformatted()メソッドを使用し、String(format:)は固定されたフォーマット文字列に対してのみ使用するのが安全なプラクティスです。

第9章: まとめと今後の展望

Swiftにおける文字列整形は、様々なツールとアプローチが利用可能です。この記事では、CスタイルのString(format:)、Swiftの文字列補間、そしてSwift 5.5以降で強化されたformatted()メソッドを中心に解説しました。

主要な文字列整形方法:

  1. String(format: ...): Cスタイルの強力なフォーマット指定子(%d, %f, %sなど)を使用。固定幅出力や特定の低レベル整形に強い。ただし、タイプセーフティに欠け、可読性が低下する場合があり、セキュリティリスクも存在する。ロケール指定も可能 (String(format: locale: ...)).
  2. 文字列補間 (\(...)): Swiftらしい、直感的でタイプセーフな方法。値や式の埋め込みが容易。カスタム型の表示にも対応 (CustomStringConvertible)。
  3. formatted() メソッド (Swift 5.5+): 文字列補間と組み合わせて、または単体で使用。数値、日付、通貨などの複雑なロケール対応整形を、タイプセーフかつ簡潔な構文で実現。String(format:)の多くの機能をモダンな方法で代替する。

適切な使い分け:

  • 推奨: ほとんどの場面で、タイプセーフで可読性の高い文字列補間、特に数値や日付の整形にはformatted()メソッドとの組み合わせを使用する。
  • 限定的な使用: 固定幅の表形式出力や、特定のC言語互換性が必要な場合にのみ、String(format:)を慎重に使用する。動的なフォーマット文字列には使わない。
  • より高度なカスタマイズ: formatted()ではカバーできない特定のニーズ(例: 数値のスペルアウト)には、NumberFormatterDateFormatterを使用する。

Swiftの文字列整形機能は進化しています。特にSwift 5.5で導入されたformatted()メソッドは、これまでのString(format:)やFormatterクラスに代わる、強力かつ現代的な選択肢を提供しています。今後もSwiftの標準ライブラリやFoundationフレームワークの進化により、より表現力豊かで効率的な文字列整形方法が登場する可能性があります。

String(format:)はSwiftの初期から存在し、CやObjective-Cのコードからの移行に役立つ互換性機能として重要です。しかし、新しいSwiftコードにおいては、文字列補間とformatted()メソッドが推奨されるアプローチであり、これらの機能を使いこなすことが、より安全で保守性の高い、そしてSwiftらしいコードを書く鍵となります。

この記事を通じて、Swiftで文字列を自在に整形するための様々な手法とその詳細を理解し、日々のコーディングに役立てていただければ幸いです。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール