【Swift】 文字コードの変換 を整理してみた

前にやってみた、これ。


2つの型(プロトコル)の間を相互に変換するだけでも、いろんな書き方があるのだな。

などと思いつつ気になってたのが文字コードの変換。

👉 文字コード - Wikipedia hatena-bookmark

今回、おおまかに整理しておきたい。

 

🧑🏻‍💻 String の内部エンコーディングは UTF-8

Swift 5 switches the preferred encoding of strings from UTF-16 to UTF-8 while preserving efficient Objective-C-interoperability.

👉 Swift.org - UTF-8 String hatena-bookmark

String 内部のエンコーディングは「UTF-8」になったらしいです。

 

🧑🏻‍💻 String、 Data とファイル間での相互変換

図で。大体こんな。

まずは、エンコーディングの引数名が、encoding:, using:, as: と多様なので初見、分かりづらかった。


「String から」または「Stringへ」変換する場合に

「相手」 (「変換先」または「変換元」) のエンコーディングを指定する。

String 自体は「UTF-8」として意識。

Data と ファイル間の変換にはエンコーディングの指定は不要。

Data はそれぞれに変換済みのバイトシーケンス。

 

🧑🏻‍💻 変換してみる

コードを書いて試してみます。

👉 【Swift】よく使いそうな String の format hatena-bookmark


"あいうえお"
  .data(using: .shiftJIS)!.map { String(format: "%02X", $0) }.joined()
// 10 bytes 82A082A282A482A682A8

からスタートして一通り。

以前書いた extension を使っています。

👉 【Swift】ファイルやディレクトリ操作するための extension をまずは作った hatena-bookmark

これで大体いけるはず。

注意としては、「変換できない文字もある」ということ。


o  Shift_JIS → UTF-8 
x  UTF-8     → Shift_JIS

対応する文字がない。

 

🧑🏻‍💻 参考

👉 String.Encoding | Apple Developer Documentation hatena-bookmark
👉 shiftJIS | Apple Developer Documentation hatena-bookmark
👉 init(data:encoding:) | Apple Developer Documentation hatena-bookmark
👉 init(contentsOf:encoding:) | Apple Developer Documentation hatena-bookmark
👉 data(using:allowLossyConversion:) | Apple Developer Documentation hatena-bookmark
👉 write(to:atomically:encoding:) | Apple Developer Documentation hatena-bookmark
👉 init(contentsOf:options:) | Apple Developer Documentation hatena-bookmark
👉 write(to:options:) | Apple Developer Documentation hatena-bookmark



【Swift】複数要素の並び替えには Tuple を使うとよい

こんなデータがあったとして、


struct User {
  var last: String
  var first: String
  var age: Int
}

let users = [
  User(last: "さとう", first: "はると", age: 19),
  User(last: "さとう", first: "しげる", age: 46),
  User(last: "いとう", first: "はると", age: 15),
  User(last: "いとう", first: "しげる", age: 50)
]

users
  .forEach { user in
    print("\(user.last)\(user.first) \(user.age)")
  }


さとうはると 19
さとうしげる 46
いとうはると 15
いとうしげる 50

並び替えたいですよね。

Tuple を使うとこんな書き方ができるんですね。


users
  .sorted(
    by: {
      ($0.last, $0.first, $0.age) < ($1.last, $1.first, $1.age)
    }
  )
  .forEach {
    print("\($0.last)\($0.first) \($0.age)")
  }


いとうしげる 50
いとうはると 15
さとうしげる 46
さとうはると 19

便利です。

少し書き換えて、年齢順で。


users
  .map {
    ($0.age, $0.last, $0.first)
  }
  .sorted(
    by: {
      $0 < $1
    }
  )
  .forEach {
    print("\($0.1)\($0.2) \($0.0)")
  }


いとうはると 15
さとうはると 19
さとうしげる 46
いとうしげる 50

これで、並び替えは自在ですね!

👉 [Swift] Tuple タプルの七不思議 #tuple - Qiita hatena-bookmark


【Swift】URL から パス と ファイル名 を区別する方法

ディレクトリを指すURL。


print(URL.documentsDirectory)

ファイルを指すURL。


print(URL.documentsDirectory.appending(component: "new.txt"))

ちょっと分かりづらいので置き換えて。

👉 【Swift】ファイルやディレクトリのパスが長すぎていやだ - URL.shortPath() hatena-bookmark


print(URL.documentsDirectory.shortPath())
// /HOME/Documents/

print(URL.documentsDirectory.appending(component: "new.txt").shortPath())
// /HOME/Documents/new.txt

そもそも、URL.path() は、

URLが


ディレクトリを指す場合は path() の末尾は「/ (スラッシュ)」

というきまりがありますね。

そこで、文字列の分割を2つの関数でやってみます。


let path = URL.documentsDirectory.shortPath()
print(path.split(separator: "/"))
print(path.components(separatedBy: "/"))

// ["HOME", "Documents"]
// ["", "HOME", "Documents", ""]

そんな違いがありますので components(separatedBy:) を使って、


let urlPath = URL.documentsDirectory.shortPath()
let components = urlPath.components(separatedBy: "/")
print("urlPath:", urlPath)
print("path:", components.dropLast().joined(separator: "/") + "/")
print("file:", components.last!)

// urlPath: /HOME/Documents/
// path: /HOME/Documents/
// file:


let urlPath = URL.documentsDirectory.appending(component: "new.txt").shortPath()
let components = urlPath.components(separatedBy: "/")
print("urlPath:", urlPath)
print("path:", components.dropLast().joined(separator: "/") + "/")
print("file:", components.last!)

// urlPath: /HOME/Documents/new.txt
// path: /HOME/Documents/
// file: new.txt

よって、components(separatedBy:) を使った場合、

最終の文字列は、


URLがディレクトリを指してる場合 → 空文字
URLがファイルを指してる場合    → ファイル名

ということになります、

という APIの仕様と既存関数の特性を使った文字列分割でした。

そもそもは、


URL.pathComponents


URL.lastPathComponent

では、ディレクトリかファイルかの区別がない。

ということからこんなことをやってしまいました。

 

🤔 参考