アプリ開発をしていると、ちょくちょく出てくるUUIDという単語、とりあえずは識別子として使うものだなと認識されている方がほとんどだと思います。
この記事では改めて、UUIDという概念、内部でどういう扱いになっているのか、Tipsなど広い視点でざっくり学びを深くしていきます。
まず「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 の List や ForEach を使うときに Identifiable プロトコルや id: パラメータがよく出てきますが、それもまさに「どの要素がどれなのか、ちゃんと識別したいから」という理屈で使われています。
次に、このそもそものIDという概念を元に、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にしておく」
くらいの感覚で大丈夫です。
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" のような文字列型としては:
uuidはUUID型uuidTextはString型
です。
文字列から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は同じか?」
といった判定が簡単にできるようになります。
ポイント:
- アプリ開発では、データを一意に識別するためのIDが必須
- UUIDは「ほぼ二度とかぶらないランダムなID」として使える
- Swiftには
UUID型が最初から用意されていて、UUID()で簡単に生成できる uuid.uuidStringで文字列に、UUID(uuidString:)で文字列からUUIDに戻せる- UUID同士は
==で比較できる
次の Chapter 2 では、この UUID を 実際にモデルや SwiftUI の List / ForEach でどう活かすか を具体的なコード付きで見ていきます 。
一番よく見るパターン:
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 にするのが基本 です。
Identifiable にしておくと、SwiftUIの List や ForEach がとても書きやすくなります。
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を指定していない
なのに、コンパイラが怒らないことです。
これは、Todo が Identifiable で、id を持っているおかげです。
SwiftUIは「この配列の要素は id で識別できるんだな」と理解してくれます。
id: \.id と id: \.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です。
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 letやguard letで安全に取り出す - 変換失敗したときのログやエラーハンドリングを忘れない
といったところがポイントです。
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に保存する場合
UserDefaults は UUID を直接保存できないので、
基本的には 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です。
ポイント:
- モデルに
UUID型のidを持たせてIdentifiableにすると、List / ForEach がとても扱いやすくなる - SwiftUIの
ListやForEachは、Identifiableな要素をidで追跡してくれる - UUIDは
uuidStringで String に、UUID(uuidString:)で String から戻せる UUIDはCodable準拠済みなので、JSONエンコード/デコードでもそのまま使える- UserDefaults などにはUUIDを直接保存できないので、文字列として保存→復元 する
ざっくり答え:
「理論上は0じゃないけど、現実的には気にしなくていいレベル でかぶらない」
UUID(特によく使われる v4)は128ビットの値で、
組み合わせの数はざっくり 3.4 × 10^38 通りあります。
イメージ的には:
- 地球上のすべてのコンピュータが
- 毎秒何億個ものUUIDを
- 宇宙の寿命レベルの時間ずっと生成し続けても
「かぶるかもね」とやっと言えるくらい のレベルと考えればOK。
初心者〜中級のアプリ開発では:
- ToDoアプリ
- メモアプリ
- 普通の業務アプリ
などで使う分には「衝突」を心配する必要はほぼありません。
日常的なアプリ開発では「ユニークなIDが欲しい → UUIDでOK」という理解で十分です。
結論:ふつうのアプリ規模なら、気にしなくて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ここは少し整理しておきましょう。
「何のIDか?」を意識する
UUIDといっても、用途はいろいろあります。
- ✅ データ1件のID
- 例:Todo、メモ、投稿、コメントなど
- ✅ ローカルで一意なID
- 例:この端末で作った「設定」のID など
- ⚠️ ユーザーID(人間そのもののID)
- ⚠️ 端末ID(このデバイスそのものを識別)
ユーザーIDにUUIDを使って「はい終わり」だと、困るケースがあります。
端末をまたいでも同じユーザーにしたい場合
例えば:
- iPhone と iPad で同じアカウントを使いたい
- 機種変更しても同じユーザーとして扱いたい
こういう場合は、どこかで「ユーザーアカウントの管理」をする必要があります。
- サーバー側でユーザー登録 → サーバーがUUID(または別のID)を発行
- Apple / Google / 自社アカウントなどでログイン → そのアカウントIDを使う
など、「サーバーや認証システムが管理するID」を使うことが多いです。
ローカルだけで完結するユーザーもどき → UUIDでOK
本物の「ログインユーザー」 → 認証システム側のIDを使う
くらいに分けて考えるとスッキリします。
- ✅ 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連携でも扱いやすい
UUIDはCodable準拠 → JSONエンコード/デコードOK- UserDefaults などには文字列として保存して使う
- よくあるミスは、View側で
UUID()を乱用すること- IDは基本「モデル側で1回だけ生成」する
とりあえず一意のIDが簡単にほしいなあ = UUIDと考えればOK、文字列変換やその逆変換は実装の必要に応じてこの記事に戻ってきてもらって確認してもらう感じで良いと思います。
お読みいただきありがとうございました。
- Apple公式ドキュメント
UUID(Foundation)IdentifiableプロトコルList(SwiftUI)ForEach(SwiftUI)Codable(Encodable/Decodable)
