⚠️

SwiftLint:non_optional_string_data_conversion の落とし穴

2024/09/29に公開

この記事はQiitaで投稿した記事の移植記事です。

https://qiita.com/KaitoMuraoka/items/c79cb893dcd527d4407a

はじめに

APIから取得した data型を .utf8 の String に変換したい場合、SwiftLint ではタイトルの通り、non_optional_string_data_conversionという warning が発生します。

今回は、このnon_optional_string_data_conversionについて気づいた点があったので、ご紹介します。

使用技術

  • Swift: 5.10(6.0.0)
  • Xcode16
  • SwiftLint

non_optional_string_data_conversion とは?

さて、今回の話の中心となる non_optional_string_data_conversion とはなんでしょうか。
タイトルにもある通り、これは SwiftLint のルールの1つです。

https://realm.github.io/SwiftLint/non_optional_string_data_conversion.html

このルールの内容は、

Prefer using UTF-8 encoded strings when converting between Stringand Data
訳:StringData 間の変換時に、UTF-8エンコードされた文字列を優先的に使用する
というものです。

例えば、以下のコードはこのルールには違反しません。

Non Triggering Examples
Data("foo".utf8)
String(decoding: data, as: UTF8.self)

一方で、以下のコードは違反となり、トリガーされます。トリガーされたコードは、Xcode上で warning として表示されます。

Triggering Examples
"foo".data(using: .utf8)
String(data: data, encoding: .utf8)

注目すべきポイント

今回注目したいのは、Data 型をエンコードする際に使う、String(decoding: data, as: UTF8.self) についてです。
このメソッドの便利な点は、String 型を返す点です。

一方、Xcode上で warning が出る String(data: data, encoding: .utf8) は、Optional<String> を返します。

では、両者の違いはなんでしょうか。
String(decoding: data, as: UTF8.self) は、たとえ data がUTF-8として正しくエンコードできない場合でも、String 型として値を返します。
それに対して、String(data: data, encoding: .utf8) は、エンコードに失敗すると nil を返します。

つまり、String(decoding: data, as: UTF8.self) は、デコードに失敗した場合でも文字化けした String 型を返してしまう可能性があるということです。

したがって、このイニシャライザを使用する際は、data が確実に UTF-8 としてエンコード可能であることを確認した上で使用することをお勧めします。
以下は、この問題に関するサンプルコードです。

https://swiftfiddle.com/nabvhbfuibb77cwik7koyvroea

この問題の落とし穴

上記を踏まえて、今回注意したいのは、non_optional_string_data_conversion がSwiftLint 上では "Enabled by default: Yes"、つまりデフォルトのルールとして設定されている点です。
そのため、.swiftlint.yml ファイルに、このルールを除外する必要があります。

ルールの除外方法

このルールの除外方法は簡単です。
ファイル内で disabled_rules の配下に non_optional_string_data_conversion を追記するだけです。

.swiftlint_yml
disabled_rules:
  - ... 除外したいその他のルール
  - non_optional_string_data_conversion
  - ...

エンコードが確実ではない場合の回避方法

では、UTF-8でエンコード可能であると確実に言えない場合、どのようにしてエンコードしたら良いでしょうか。

1つの解決策として、オプショナルバインディングして、nil の場合は別の文字列を返すのが良いでしょう。
以下は、そのサンプルことです。

if let html = String(data: invalidData, encoding: .utf8) {
  print(html)
} else {
  print("エンコード失敗")
}

https://swiftfiddle.com/uebk6jrukzdmdjj6oepht7oeru

最後に

今回紹介した「落とし穴」は、String(decoding: data, as: UTF8.self) を推奨するルールが SwiftLint でデフォルトで有効化されている点にあります。
そのため、必要に応じて .swiftlint.yml に設定を追加する必要があります。
また、エンコードが確実ではない場合、オプショナルバインディングをしてあげる必要があります。

参考

https://github.com/realm/SwiftLint/issues/5263

https://www.hackingwithswift.com/example-code/language/how-to-convert-data-to-a-string

Discussion