[Xcode/Swift] UUIDを学び直してみよう

アプリ開発をしていると、ちょくちょく出てくるUUIDという単語、とりあえずは識別子として使うものだなと認識されている方がほとんどだと思います。

この記事では改めて、UUIDという概念、内部でどういう扱いになっているのか、Tipsなど広い視点でざっくり学びを深くしていきます。

1. UUIDってそもそも何?なぜ必要?(基本編)

1.1 IDってなんで必要なの?

まず「UUID」の話に行く前に、そもそも ID(識別子) がなぜ必要なのかを整理しておきます。

アプリ開発をしていると、こんな場面がよく出てきます:

  • ToDoリストの1つ1つのタスクを区別したい
  • メモアプリで、どのメモを削除するか特定したい
  • サーバーから受け取ったデータの1件1件を識別したい
  • ローカルに保存したデータの「誰なのか」「どのデータなのか」を見分けたい

シンプルな ToDo モデルを考えてみましょう。

struct Todo {
    let title: String
    let isDone: Bool
}

最初はこれで困らないかもしれませんが、「どのTodoを更新/削除するか」を考えたときに問題が出てきます。

  • 同じタイトルのTodoがあったら?
  • isDone の状態も同じだったら?

この2つの Todo を、アプリ側で区別できなくなくなってしまうことが問題になります。

そこで登場するのが ID(識別子) です。

struct Todo {
    let id: Int      // または UUID
    let title: String
    let isDone: Bool
}

id が入ったことで、

  • 「id = 1 のTodo」
  • 「id = 2 のTodo」

のように、中身のテキストが同じでも別物として扱える ようになります。

SwiftUI の ListForEach を使うときに Identifiable プロトコルや id: パラメータがよく出てきますが、それもまさに「どの要素がどれなのか、ちゃんと識別したいから」という理屈で使われています。

次に、このそもそものIDという概念を元に、UUIDを解説していきます。


1.2 UUIDとは?ざっくりイメージを掴む

UUID = ほぼ二度とかぶらないランダムなID

この理解をまずは持ってもらえればOKです、というかこの理解があれば99%は問題ないのでこれ以降の解説もすっ飛ばしてもらっても良いレベル。

  • 形式的には 128ビット(かなり長い)の値
  • 人間が読みやすい(?)ようにハイフン付きの文字列で表現されることが多い

こんな感じ:

3F2504E0-4F89-11D3-9A0C-0305E82C3301

// ざっくり分解すると
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
という形式(16進数の文字)になってる。

Intや連番IDとの違い

じゃあ、「IDなら Int で 1, 2, 3… って増やしていけばよくない?」と誰もが思うのではないでしょうか。

たしかに、同じアプリの中だけ で完結するなら、それでもOKな場合が多いです。

ただし、次のような場合に Int の連番IDは困ることになります:

  • 複数端末で別々にデータを作って、あとでサーバーでマージしたい
  • サーバーとクライアント、いろんなシステムがそれぞれ勝手にIDを振ってくる
  • 「IDを発行する場所」を単一に決めたくない(中央集権にしたくない)

このとき、「世界のどこで作っても、ほぼかぶらないID」 があると、とても扱いやすくなるということです。

  • Intの連番
    • 人間には分かりやすい
    • 発行する場所を1ヶ所にする必要があることが多い
  • UUID
    • ほぼ絶対にかぶらない
    • どこで生成してもOK(端末ごとに勝手に生成しても平気)
    • ちょっと長くて見づらいけど、アプリやサーバーが扱うなら問題なし

初心者のうちは、

他と絶対かぶらないIDが欲しいときは、とりあえずUUIDにしておく

くらいの感覚で大丈夫です。


1.3 SwiftでのUUID型の基本

Swift には最初から UUID という型が用意されています。
自作する必要はありません。

UUIDの生成方法

一番基本的な生成方法はこちら:

let uuid = UUID()
print(uuid)

// 出力例
E9246C99-9F71-4E5B-9EDE-674F676F0F48

毎回 UUID() を呼ぶたびに、新しいランダムなUUID が生成されます。

文字列に変換する(uuidString)

サーバーとやり取りしたり、ファイルやUserDefaultsに保存したいときは、
「String として扱いたい」場面があります。

そのときに使うのが uuidString プロパティです。

let uuid = UUID()
let uuidText = uuid.uuidString

print(uuidText) // "E9246C99-9F71-4E5B-9EDE-674F676F0F48" のような文字列

型としては:

  • uuidUUID
  • uuidTextString

です。

文字列からUUIDに戻す

逆に、文字列として保存しておいたIDを、再び UUID 型に戻したいときは
UUID(uuidString:) イニシャライザを使います。

let savedText = "E9246C99-9F71-4E5B-9EDE-674F676F0F48"

if let uuid = UUID(uuidString: savedText) {
    print("復元できました: \(uuid)")
} else {
    print("UUIDとして不正な文字列でした")
}
  • 正しい形式の文字列なら UUID インスタンスが返ってくる
  • 間違った文字列なら nil になる(UUID? 型)

UUID同士の比較(== でOK)

UUIDは「同じかどうか」をチェックしたい場面が多いですが、
比較はふつうの値型と同じように == が使えます。

let uuid1 = UUID()
let uuid2 = UUID()
let uuid3 = uuid1

print(uuid1 == uuid2) // ほぼ必ず false
print(uuid1 == uuid3) // true

この性質を利用して、

  • 「このTodoはさっきタップしたTodoと同じか?」
  • 「サーバーから返ってきたIDとローカルに持っているIDは同じか?」

といった判定が簡単にできるようになります。


1章のまとめ

ポイント:

  • アプリ開発では、データを一意に識別するためのIDが必須
  • UUIDは「ほぼ二度とかぶらないランダムなID」として使える
  • Swiftには UUID 型が最初から用意されていて、UUID() で簡単に生成できる
  • uuid.uuidString で文字列に、UUID(uuidString:) で文字列からUUIDに戻せる
  • UUID同士は == で比較できる

次の Chapter 2 では、この UUID を 実際にモデルや SwiftUI の List / ForEach でどう活かすか を具体的なコード付きで見ていきます 。


2. Swift / SwiftUIでのUUIDの使いどころ(実践&応用編)

2.1 モデルにUUIDを持たせる

一番よく見るパターン:

struct Todo: Identifiable {
    let id: UUID = UUID()
    let title: String
    var isDone: Bool
}

ポイントはこの3つ:

  • id プロパティを持っている
  • 型が UUID
  • Identifiable に準拠している

Identifiableとは?

Identifiable

「この型は自分を一意に識別するための id を持っていますよ」

ということをコンパイラに伝えるためのプロトコルです。

protocol Identifiable {
    associatedtype ID : Hashable
    var id: Self.ID { get }
}

と定義されているので、

  • id というプロパティが必須
  • その型は Hashable であればOK
  • UUID はすでに Hashable 準拠済み

だから、id: UUID はとても相性がいいという理屈。

id は let にしておくのが基本

struct Todo: Identifiable {
    var id = UUID()   // ← これはあまりよくない
    ...
}

var にしてしまうと、後からIDを変えられてしまう ので、
「これ、結局どのTodoなんだっけ?」と分からなくなります。

IDは一度決めたら変えない、という意味で let にするのが基本 です。


2.2 List / ForEach でのUUID活用

Identifiable にしておくと、SwiftUIの ListForEach がとても書きやすくなります。

struct TodoListView: View {
    @State private var todos: [Todo] = [
        Todo(title: "買い物に行く", isDone: false),
        Todo(title: "ブログを書く", isDone: false),
        Todo(title: "散歩する", isDone: true)
    ]

    var body: some View {
        List(todos) { todo in
            HStack {
                Text(todo.title)
                Spacer()
                if todo.isDone {
                    Image(systemName: "checkmark.circle.fill")
                        .foregroundStyle(.green)
                }
            }
        }
    }
}

ポイント:

List(todos) { todo in ... } とだけ書いているのにid を指定していない

なのに、コンパイラが怒らないことです。

これは、TodoIdentifiable で、id を持っているおかげです。
SwiftUIは「この配列の要素は id で識別できるんだな」と理解してくれます。

id: \.idid: \.self の違い(軽く)

Identifiable にしていない型を List に渡すときは、こんな書き方もします:

struct NumberListView: View {
    let numbers = [1, 2, 3, 4]

    var body: some View {
        List(numbers, id: \.self) { number in
            Text("\(number)")
        }
    }
}

ここで id: \.self は、

「この要素自体の値をIDとして使ってね」

という指定になります。

一方、UUID を自分で用意している場合は id: \.id を使うことが多いです:

List(todos, id: \.id) { todo in
    Text(todo.title)
}

Identifiable を付けていれば、id: \.id を書かなくても済む、くらいの理解でOKです。


2.3 UUIDとString / 他の型との変換

Chapter1でも触れましたが、ここではもう少し実務寄りに見てみます。

モデル内でUUIDとStringを両方持つパターン

たとえば、サーバー側のAPIが「IDをStringで返してくる」ことはよくあります。

そんなときに、アプリ側では UUID と Stringを変換して使うことがあります。

struct User: Identifiable, Codable {
    let id: UUID
    let name: String

    var idText: String {
        id.uuidString
    }
}

// idText を使えば、ログ出力やデバッグ時に簡単に文字列として出せる
print(user.idText)  // "E9246C99-9F71-4E5B-9EDE-674F676F0F48" など

String → UUID に変換する

外部から渡ってきた文字列IDを UUID に変換したいとき:

func uuid(from text: String) -> UUID? {
    UUID(uuidString: text)
}

if let id = uuid(from: someText) {
    // ここでUUIDとして扱える
} else {
    // 不正な形式だった
}

ここで UUID? になることに注意。

  • 必ず if letguard let で安全に取り出す
  • 変換失敗したときのログやエラーハンドリングを忘れない

といったところがポイントです。


2.4 永続化やネットワークでのUUID

UUIDは「ただのID」ですが、
JSONで送受信したり、ローカルに保存するときにどう扱うか は、ちょっと知っておくと便利です。

Codable と UUID

Swift の UUID は、Codable にすでに準拠しています。
つまり、次のようなモデルはそのままJSONエンコード/デコードできます。

struct Todo: Identifiable, Codable {
    let id: UUID
    let title: String
    var isDone: Bool
}

// エンコードすると(ざっくりですが)こんなJSONになる
{
  "id": "E9246C99-9F71-4E5B-9EDE-674F676F0F48",
  "title": "買い物に行く",
  "isDone": false
}

UUIDが自動的に文字列として出力されるので、
サーバー側も「文字列としてのID」として扱えばOKです。

let todo = Todo(id: UUID(), title: "ブログを書く", isDone: false)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted

if let jsonData = try? encoder.encode(todo),
   let jsonString = String(data: jsonData, encoding: .utf8) {
    print(jsonString)
}

UserDefaultsに保存する場合

UserDefaultsUUID を直接保存できないので、
基本的には Stringとして保存 することが多いです。

let key = "currentUserID"

// 保存
let userID = UUID()
UserDefaults.standard.set(userID.uuidString, forKey: key)

// 読み込み
if let idText = UserDefaults.standard.string(forKey: key),
   let restoredID = UUID(uuidString: idText) {
    print("復元できたID: \(restoredID)")
} else {
    print("まだIDが保存されていない or 不正な文字列")
}

ポイント:

  • 保存時 → uuid.uuidString にして保存
  • 読み込み時 → UUID(uuidString:) で戻す
  • 戻せなかったときの nil ケースを忘れずにハンドリング

ローカルファイル(JSON)に保存する場合

ローカルにJSONファイルとして保存するパターンでも、
Codable + UUID の組み合わせがそのまま使えます。

func saveTodos(_ todos: [Todo], to url: URL) throws {
    let encoder = JSONEncoder()
    let data = try encoder.encode(todos)
    try data.write(to: url)
}

func loadTodos(from url: URL) throws -> [Todo] {
    let data = try Data(contentsOf: url)
    let decoder = JSONDecoder()
    return try decoder.decode([Todo].self, from: data)
}

UUIDの変換は Codable が全部やってくれるので、
アプリ側は「ただの UUID 型のプロパティ」として扱うだけでOKです。


2章のまとめ

ポイント:

  • モデルに UUID 型の id を持たせて Identifiable にすると、List / ForEach がとても扱いやすくなる
  • SwiftUIの ListForEach は、Identifiable な要素を id で追跡してくれる
  • UUIDは uuidString で String に、UUID(uuidString:) で String から戻せる
  • UUIDCodable 準拠済みなので、JSONエンコード/デコードでもそのまま使える
  • UserDefaults などにはUUIDを直接保存できないので、文字列として保存→復元 する

3. UUIDのよくある質問&Tips集

Q1. UUIDって本当に「かぶらない」のか?

ざっくり答え:

「理論上は0じゃないけど、現実的には気にしなくていいレベル でかぶらない」

UUID(特によく使われる v4)は128ビットの値で、
組み合わせの数はざっくり 3.4 × 10^38 通りあります。

イメージ的には:

  • 地球上のすべてのコンピュータが
  • 毎秒何億個ものUUIDを
  • 宇宙の寿命レベルの時間ずっと生成し続けても

「かぶるかもね」とやっと言えるくらい のレベルと考えればOK。

初心者〜中級のアプリ開発では:

  • ToDoアプリ
  • メモアプリ
  • 普通の業務アプリ

などで使う分には「衝突」を心配する必要はほぼありません。

日常的なアプリ開発では「ユニークなIDが欲しい → UUIDでOK」という理解で十分です。


Q2. UUIDを乱発しても大丈夫?パフォーマンス的に問題ない?

結論:ふつうのアプリ規模なら、気にしなくてOK。

  • 1画面に表示するアイテム数が数十〜数百件
  • せいぜい数千〜数万件のデータを扱う程度

といった普通のアプリでは、UUIDを生成するコストがボトルネックになることはまずありません。

ただし、やってはいけないパターン もあります。

❌ ダメな例:Viewの中で毎回 UUID() する

struct BadView: View {
    var body: some View {
        List {
            ForEach(0..<100) { _ in
                Text("Row")
                    .id(UUID())  // ← 毎回違うIDになってしまう
            }
        }
    }
}

これをやると:

  • Viewが更新されるたびに 毎回新しいUUIDが生成される
  • SwiftUI は「全部別物のViewだ」と思ってしまい、差分更新が効かなくなる

結果として:

  • アニメーションがガタガタする
  • スクロール位置が飛んだりする
  • 無駄にViewの再生成が増える

などの問題が出ます。

✅ 良い例:モデル側で一度だけUUIDを作る

struct Item: Identifiable {
    let id = UUID()    // 初期化時に1回だけ
    let title: String
}

struct GoodView: View {
    let items: [Item]

    var body: some View {
        List(items) { item in
            Text(item.title)
        }
    }
}

// UUIDは 「生成は1回だけ、あとは使い回す」 が基本
// Viewの中で毎回 UUID() するのはほぼNGと思ってOK

Q3. ユーザーIDにUUIDを使ってもいいの?

ここは少し整理しておきましょう。

「何のIDか?」を意識する

UUIDといっても、用途はいろいろあります。

  • データ1件のID
    • 例:Todo、メモ、投稿、コメントなど
  • ローカルで一意なID
    • 例:この端末で作った「設定」のID など
  • ⚠️ ユーザーID(人間そのもののID)
  • ⚠️ 端末ID(このデバイスそのものを識別)

ユーザーIDにUUIDを使って「はい終わり」だと、困るケースがあります。

端末をまたいでも同じユーザーにしたい場合

例えば:

  • iPhone と iPad で同じアカウントを使いたい
  • 機種変更しても同じユーザーとして扱いたい

こういう場合は、どこかで「ユーザーアカウントの管理」をする必要があります。

  • サーバー側でユーザー登録 → サーバーがUUID(または別のID)を発行
  • Apple / Google / 自社アカウントなどでログイン → そのアカウントIDを使う

など、「サーバーや認証システムが管理するID」を使うことが多いです。

ローカルだけで完結するユーザーもどき → UUIDでOK
本物の「ログインユーザー」 → 認証システム側のIDを使う

くらいに分けて考えるとスッキリします。


UUIDを使うときの簡単ベストプラクティス

  • IDはUUID + Identifiableで持たせる
    • struct Todo: Identifiable { let id = UUID() }
  • IDはletで不変にする
  • Viewの中で毎回 UUID() しない
    • モデル側で1回生成 → それを使い回す
  • 永続化やネットワークではStringに変換してもOK
    • 保存:uuid.uuidString
    • 復元:UUID(uuidString:)
  • ユーザー/端末のIDと、データ1件のIDは分けて考える
  • ✅ 迷ったら「とりあえずUUID」でOK(特にローカルのデータID)

この理解があればOK。


総括

  • IDは「データを一意に識別するためのラベル」
    • リストの要素、Todo、メモなど、「どれがどれか」を区別するために必須
  • UUIDは「ほぼかぶらないランダムID」
    • 128ビットの巨大な空間 → 実質かぶりを気にしなくていい
  • Swiftには UUID 型が標準で用意されている
    • UUID() で生成
    • uuid.uuidString で文字列へ
    • UUID(uuidString:) で文字列から復元
  • SwiftUIとは Identifiable とセットで使うのが定番
    • struct Todo: Identifiable { let id = UUID() }
    • List(todos)ForEach(todos) でスッキリ書ける
  • 永続化やAPI連携でも扱いやすい
    • UUIDCodable 準拠 → JSONエンコード/デコードOK
    • UserDefaults などには文字列として保存して使う
  • よくあるミスは、View側で UUID() を乱用すること
    • IDは基本「モデル側で1回だけ生成」する

とりあえず一意のIDが簡単にほしいなあ = UUIDと考えればOK、文字列変換やその逆変換は実装の必要に応じてこの記事に戻ってきてもらって確認してもらう感じで良いと思います。

お読みいただきありがとうございました。