Realm Swift

Realm Swiftは「iOSとmacOSアプリケーション向けのオブジェクトデータベース」として設計された、Swiftネイティブなデータベース解決策です。SQLiteの代替として開発され、オブジェクト指向の直感的なAPIを通じて、複雑なSQLクエリなしに高性能なデータ操作を実現します。リアクティブプログラミング、自動UI更新、スレッドセーフティ、暗号化機能を内蔵し、モバイルアプリケーション開発に特化した最適化により、iOS/macOSの高品質なアプリケーション開発をサポートします。

SwiftデータベースオブジェクトデータベースiOSmacOSモバイル

ライブラリ

Realm Swift

概要

Realm Swiftは「iOSとmacOSアプリケーション向けのオブジェクトデータベース」として設計された、Swiftネイティブなデータベース解決策です。SQLiteの代替として開発され、オブジェクト指向の直感的なAPIを通じて、複雑なSQLクエリなしに高性能なデータ操作を実現します。リアクティブプログラミング、自動UI更新、スレッドセーフティ、暗号化機能を内蔵し、モバイルアプリケーション開発に特化した最適化により、iOS/macOSの高品質なアプリケーション開発をサポートします。

詳細

Realm Swift 2025年版は、Swift 6とSwiftUIとの完全統合により、現代的なiOS/macOSアプリケーション開発に最適化されています。自動スキーママイグレーション、リアルタイムデータ同期、オフライン・ファースト設計により、ユーザーエクスペリエンスを最優先とした堅牢なデータ層を提供します。Core DataやSQLiteと比較して、格段にシンプルなAPIと高い開発効率を実現しつつ、エンタープライズレベルのセキュリティ(AES-256暗号化)と性能を両立します。@Observable、@State、async/awaitとの自然な統合により、SwiftUIでのリアクティブなデータバインディングが簡単に実装できます。

主な特徴

  • オブジェクト指向API: Swiftのオブジェクトとしてデータベースアクセス
  • リアクティブUI: 自動的なUI更新とデータバインディング
  • 高性能: SQLiteよりも高速なデータアクセス
  • スレッドセーフ: マルチスレッド環境での安全なデータ操作
  • 暗号化: AES-256による包括的データ保護
  • SwiftUI統合: ネイティブなSwiftUIサポート

メリット・デメリット

メリット

  • SQLクエリ不要で直感的なオブジェクト指向データ操作
  • 自動的なUI更新による開発効率の大幅向上
  • SQLiteやCore Dataを凌駕する高いパフォーマンス
  • スレッドセーフな設計で安全なマルチスレッドプログラミング
  • エンタープライズグレードの暗号化とセキュリティ機能
  • SwiftUIとの自然な統合とリアクティブプログラミング対応

デメリット

  • iOS/macOS/watchOSに限定されたプラットフォーム固有性
  • 大規模データセットでのメモリ使用量増加の可能性
  • SQLの豊富な機能と比較したクエリ機能の制限
  • 他プラットフォームへの移植困難性
  • SQLiteほどの普及度ではなく、専門知識が必要
  • プロダクション環境でのバックアップとレプリケーション機能の限界

参考ページ

書き方の例

セットアップ

// Package.swift
// dependencies: [
//     .package(url: "https://github.com/realm/realm-swift.git", from: "10.45.0")
// ]

// CocoaPods の場合
// pod 'RealmSwift', '~> 10.45.0'

import RealmSwift
import SwiftUI
// Info.plist または設定で暗号化キーを管理
// この例は開発用です。プロダクションでは適切なキーストア管理を行ってください

import Foundation
import Security

class RealmEncryptionKeyManager {
    private static let keyIdentifier = "RealmEncryptionKey"
    
    static func getOrCreateEncryptionKey() -> Data {
        if let existingKey = getEncryptionKeyFromKeychain() {
            return existingKey
        }
        
        let newKey = generateRandomKey()
        saveEncryptionKeyToKeychain(newKey)
        return newKey
    }
    
    private static func generateRandomKey() -> Data {
        var keyData = Data(count: 64) // 512-bit key for AES-256
        let result = keyData.withUnsafeMutableBytes { mutableBytes in
            SecRandomCopyBytes(kSecRandomDefault, 64, mutableBytes.bindMemory(to: UInt8.self).baseAddress!)
        }
        guard result == errSecSuccess else {
            fatalError("Failed to generate random key")
        }
        return keyData
    }
    
    private static func saveEncryptionKeyToKeychain(_ key: Data) {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: keyIdentifier,
            kSecValueData as String: key
        ]
        SecItemAdd(query as CFDictionary, nil)
    }
    
    private static func getEncryptionKeyFromKeychain() -> Data? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: keyIdentifier,
            kSecReturnData as String: kCFBooleanTrue!,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]
        
        var dataTypeRef: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
        
        if status == errSecSuccess {
            return dataTypeRef as? Data
        }
        return nil
    }
}

基本的なモデル定義

import RealmSwift
import Foundation

// 基本ユーザーモデル
class User: Object, ObjectKeyIdentifiable {
    @Persisted var id: ObjectId = ObjectId.generate()
    @Persisted var name: String = ""
    @Persisted var email: String = ""
    @Persisted var age: Int = 0
    @Persisted var isActive: Bool = true
    @Persisted var createdAt: Date = Date()
    @Persisted var updatedAt: Date = Date()
    @Persisted var tags: List<String> = List<String>()
    @Persisted var metadata: Map<String, AnyRealmValue> = Map<String, AnyRealmValue>()
    
    // リレーション(逆方向)
    @Persisted(originProperty: "author") var posts: LinkingObjects<Post>
    @Persisted var profile: Profile?
    
    // Primary Key
    override static func primaryKey() -> String? {
        return "id"
    }
    
    // インデックス設定
    override static func indexedProperties() -> [String] {
        return ["email", "isActive", "createdAt"]
    }
    
    // 計算プロパティ
    var fullDisplayName: String {
        return name.isEmpty ? "Anonymous User" : name
    }
    
    var isAdult: Bool {
        return age >= 18
    }
    
    var postCount: Int {
        return posts.count
    }
    
    // オブジェクトライフサイクル
    override func willSave() {
        super.willSave()
        updatedAt = Date()
    }
}

// プロフィール情報(埋め込み型)
class Profile: EmbeddedObject {
    @Persisted var bio: String = ""
    @Persisted var website: String = ""
    @Persisted var location: String = ""
    @Persisted var avatarURL: String = ""
    @Persisted var socialLinks: Map<String, String> = Map<String, String>()
    @Persisted var skills: List<String> = List<String>()
    @Persisted var experienceYears: Int = 0
    @Persisted var isPublic: Bool = true
    
    var hasCompleteProfile: Bool {
        return !bio.isEmpty && !location.isEmpty && skills.count > 0
    }
}

// 投稿モデル
class Post: Object, ObjectKeyIdentifiable {
    @Persisted var id: ObjectId = ObjectId.generate()
    @Persisted var title: String = ""
    @Persisted var content: String = ""
    @Persisted var excerpt: String = ""
    @Persisted var isPublished: Bool = false
    @Persisted var publishedAt: Date?
    @Persisted var createdAt: Date = Date()
    @Persisted var updatedAt: Date = Date()
    @Persisted var viewCount: Int = 0
    @Persisted var tags: List<String> = List<String>()
    @Persisted var featuredImageURL: String = ""
    
    // リレーション
    @Persisted var author: User?
    @Persisted var category: Category?
    @Persisted var comments: List<Comment> = List<Comment>()
    
    override static func primaryKey() -> String? {
        return "id"
    }
    
    override static func indexedProperties() -> [String] {
        return ["isPublished", "publishedAt", "createdAt", "author"]
    }
    
    var isPublic: Bool {
        return isPublished && publishedAt != nil
    }
    
    var readingTimeMinutes: Int {
        let wordsPerMinute = 200
        let wordCount = content.split(separator: " ").count
        return max(1, wordCount / wordsPerMinute)
    }
    
    override func willSave() {
        super.willSave()
        updatedAt = Date()
        
        // 抜粋の自動生成
        if excerpt.isEmpty && !content.isEmpty {
            excerpt = String(content.prefix(200))
        }
        
        // 公開日時の設定
        if isPublished && publishedAt == nil {
            publishedAt = Date()
        }
    }
}

// カテゴリモデル
class Category: Object, ObjectKeyIdentifiable {
    @Persisted var id: ObjectId = ObjectId.generate()
    @Persisted var name: String = ""
    @Persisted var description: String = ""
    @Persisted var color: String = "#007AFF"
    @Persisted var isActive: Bool = true
    @Persisted var sortOrder: Int = 0
    @Persisted var createdAt: Date = Date()
    
    @Persisted(originProperty: "category") var posts: LinkingObjects<Post>
    
    override static func primaryKey() -> String? {
        return "id"
    }
    
    override static func indexedProperties() -> [String] {
        return ["name", "isActive", "sortOrder"]
    }
    
    var postCount: Int {
        return posts.filter("isPublished == true").count
    }
}

// コメントモデル
class Comment: EmbeddedObject {
    @Persisted var id: ObjectId = ObjectId.generate()
    @Persisted var content: String = ""
    @Persisted var authorName: String = ""
    @Persisted var authorEmail: String = ""
    @Persisted var isApproved: Bool = false
    @Persisted var isSpam: Bool = false
    @Persisted var createdAt: Date = Date()
    @Persisted var ipAddress: String = ""
    @Persisted var userAgent: String = ""
    
    var isValid: Bool {
        return !content.isEmpty && !authorName.isEmpty && content.count >= 5
    }
}

基本的なCRUD操作

import RealmSwift

class UserRepository: ObservableObject {
    private let realm: Realm
    
    init() throws {
        // 暗号化設定
        let encryptionKey = RealmEncryptionKeyManager.getOrCreateEncryptionKey()
        let config = Realm.Configuration(
            encryptionKey: encryptionKey,
            schemaVersion: 1,
            migrationBlock: { migration, oldSchemaVersion in
                // マイグレーション処理
                if oldSchemaVersion < 1 {
                    // 必要に応じてマイグレーション処理を実装
                }
            }
        )
        
        self.realm = try Realm(configuration: config)
    }
    
    // ユーザー作成
    func createUser(name: String, email: String, age: Int) async throws -> User {
        let user = User()
        user.name = name
        user.email = email
        user.age = age
        user.createdAt = Date()
        
        return try await MainActor.run {
            try realm.write {
                realm.add(user)
                return user
            }
        }
    }
    
    // ユーザー取得
    func getUser(by id: ObjectId) -> User? {
        return realm.object(ofType: User.self, forPrimaryKey: id)
    }
    
    // 全ユーザー取得
    func getAllUsers() -> Results<User> {
        return realm.objects(User.self).sorted(byKeyPath: "createdAt", ascending: false)
    }
    
    // アクティブユーザー取得
    func getActiveUsers() -> Results<User> {
        return realm.objects(User.self).filter("isActive == true").sorted(byKeyPath: "name")
    }
    
    // 名前検索
    func searchUsers(by namePattern: String) -> Results<User> {
        return realm.objects(User.self)
            .filter("name CONTAINS[c] %@ OR email CONTAINS[c] %@", namePattern, namePattern)
            .sorted(byKeyPath: "name")
    }
    
    // 年齢範囲フィルター
    func getUsersByAgeRange(min: Int, max: Int) -> Results<User> {
        return realm.objects(User.self)
            .filter("age >= %@ AND age <= %@", min, max)
            .sorted(byKeyPath: "age")
    }
    
    // ユーザー更新
    func updateUser(_ user: User, name: String, email: String, age: Int) async throws {
        try await MainActor.run {
            try realm.write {
                user.name = name
                user.email = email
                user.age = age
                user.updatedAt = Date()
            }
        }
    }
    
    // プロフィール更新
    func updateUserProfile(_ user: User, profile: Profile) async throws {
        try await MainActor.run {
            try realm.write {
                user.profile = profile
                user.updatedAt = Date()
            }
        }
    }
    
    // ユーザー削除
    func deleteUser(_ user: User) async throws {
        try await MainActor.run {
            try realm.write {
                // 関連する投稿も削除(カスケード削除)
                realm.delete(user.posts)
                realm.delete(user)
            }
        }
    }
    
    // タグ追加
    func addTagToUser(_ user: User, tag: String) async throws {
        try await MainActor.run {
            try realm.write {
                if !user.tags.contains(tag) {
                    user.tags.append(tag)
                }
            }
        }
    }
    
    // メタデータ設定
    func setUserMetadata(_ user: User, key: String, value: AnyRealmValue) async throws {
        try await MainActor.run {
            try realm.write {
                user.metadata[key] = value
            }
        }
    }
    
    // ユーザー統計
    func getUserStatistics() -> (total: Int, active: Int, averageAge: Double) {
        let allUsers = realm.objects(User.self)
        let activeUsers = allUsers.filter("isActive == true")
        let averageAge = allUsers.average(ofProperty: "age") ?? 0.0
        
        return (
            total: allUsers.count,
            active: activeUsers.count,
            averageAge: averageAge
        )
    }
}

// 使用例
class ContentView: View {
    @StateObject private var userRepository = try! UserRepository()
    @State private var users: Results<User>?
    @State private var newUserName = ""
    @State private var newUserEmail = ""
    @State private var newUserAge = 25
    @State private var searchText = ""
    
    var body: some View {
        NavigationView {
            VStack {
                // ユーザー作成フォーム
                VStack(spacing: 10) {
                    TextField("Name", text: $newUserName)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                    
                    TextField("Email", text: $newUserEmail)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .keyboardType(.emailAddress)
                    
                    HStack {
                        Text("Age: \(newUserAge)")
                        Slider(value: Binding(
                            get: { Double(newUserAge) },
                            set: { newUserAge = Int($0) }
                        ), in: 0...100, step: 1)
                    }
                    
                    Button("Create User") {
                        Task {
                            try await createUser()
                        }
                    }
                    .disabled(newUserName.isEmpty || newUserEmail.isEmpty)
                }
                .padding()
                
                // 検索バー
                TextField("Search users...", text: $searchText)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding(.horizontal)
                
                // ユーザーリスト
                if let users = users {
                    List(users, id: \.id) { user in
                        UserRowView(user: user, repository: userRepository)
                    }
                }
                
                Spacer()
            }
            .navigationTitle("Realm Swift Demo")
            .onAppear {
                loadUsers()
            }
            .onChange(of: searchText) { _, newValue in
                searchUsers()
            }
        }
    }
    
    private func createUser() async throws {
        let _ = try await userRepository.createUser(
            name: newUserName,
            email: newUserEmail,
            age: newUserAge
        )
        
        // フォームリセット
        newUserName = ""
        newUserEmail = ""
        newUserAge = 25
        
        // リスト更新
        loadUsers()
    }
    
    private func loadUsers() {
        users = userRepository.getAllUsers()
    }
    
    private func searchUsers() {
        if searchText.isEmpty {
            users = userRepository.getAllUsers()
        } else {
            users = userRepository.searchUsers(by: searchText)
        }
    }
}

複雑なクエリとリレーション

class PostRepository: ObservableObject {
    private let realm: Realm
    
    init() throws {
        let encryptionKey = RealmEncryptionKeyManager.getOrCreateEncryptionKey()
        let config = Realm.Configuration(encryptionKey: encryptionKey)
        self.realm = try Realm(configuration: config)
    }
    
    // 投稿作成
    func createPost(title: String, content: String, author: User, category: Category?) async throws -> Post {
        let post = Post()
        post.title = title
        post.content = content
        post.author = author
        post.category = category
        
        return try await MainActor.run {
            try realm.write {
                realm.add(post)
                return post
            }
        }
    }
    
    // 公開投稿取得
    func getPublishedPosts() -> Results<Post> {
        return realm.objects(Post.self)
            .filter("isPublished == true")
            .sorted(byKeyPath: "publishedAt", ascending: false)
    }
    
    // ユーザー別投稿
    func getPostsByUser(_ user: User) -> Results<Post> {
        return realm.objects(Post.self)
            .filter("author == %@", user)
            .sorted(byKeyPath: "createdAt", ascending: false)
    }
    
    // カテゴリ別投稿
    func getPostsByCategory(_ category: Category) -> Results<Post> {
        return realm.objects(Post.self)
            .filter("category == %@ AND isPublished == true", category)
            .sorted(byKeyPath: "publishedAt", ascending: false)
    }
    
    // 複雑な検索クエリ
    func searchPosts(
        titlePattern: String? = nil,
        author: User? = nil,
        category: Category? = nil,
        publishedOnly: Bool = false,
        tags: [String] = [],
        dateRange: ClosedRange<Date>? = nil
    ) -> Results<Post> {
        var predicates: [NSPredicate] = []
        
        // タイトル検索
        if let titlePattern = titlePattern, !titlePattern.isEmpty {
            predicates.append(NSPredicate(format: "title CONTAINS[c] %@ OR content CONTAINS[c] %@", titlePattern, titlePattern))
        }
        
        // 著者フィルター
        if let author = author {
            predicates.append(NSPredicate(format: "author == %@", author))
        }
        
        // カテゴリフィルター
        if let category = category {
            predicates.append(NSPredicate(format: "category == %@", category))
        }
        
        // 公開状態フィルター
        if publishedOnly {
            predicates.append(NSPredicate(format: "isPublished == true"))
        }
        
        // タグフィルター
        if !tags.isEmpty {
            let tagPredicates = tags.map { NSPredicate(format: "ANY tags == %@", $0) }
            predicates.append(NSCompoundPredicate(orPredicateWithSubpredicates: tagPredicates))
        }
        
        // 日付範囲フィルター
        if let dateRange = dateRange {
            predicates.append(NSPredicate(format: "createdAt >= %@ AND createdAt <= %@", dateRange.lowerBound as NSDate, dateRange.upperBound as NSDate))
        }
        
        let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
        
        return realm.objects(Post.self)
            .filter(compoundPredicate)
            .sorted(byKeyPath: "createdAt", ascending: false)
    }
    
    // 人気投稿(閲覧数順)
    func getPopularPosts(limit: Int = 10) -> Results<Post> {
        return realm.objects(Post.self)
            .filter("isPublished == true")
            .sorted(byKeyPath: "viewCount", ascending: false)
            .prefix(limit)
    }
    
    // 最近の投稿統計
    func getRecentPostStats(days: Int) -> (total: Int, published: Int, drafts: Int) {
        let cutoffDate = Calendar.current.date(byAdding: .day, value: -days, to: Date()) ?? Date()
        let recentPosts = realm.objects(Post.self).filter("createdAt >= %@", cutoffDate)
        let publishedPosts = recentPosts.filter("isPublished == true")
        let draftPosts = recentPosts.filter("isPublished == false")
        
        return (
            total: recentPosts.count,
            published: publishedPosts.count,
            drafts: draftPosts.count
        )
    }
    
    // 投稿の閲覧数増加
    func incrementViewCount(_ post: Post) async throws {
        try await MainActor.run {
            try realm.write {
                post.viewCount += 1
            }
        }
    }
    
    // 投稿公開
    func publishPost(_ post: Post) async throws {
        try await MainActor.run {
            try realm.write {
                post.isPublished = true
                post.publishedAt = Date()
            }
        }
    }
    
    // コメント追加
    func addComment(to post: Post, content: String, authorName: String, authorEmail: String) async throws {
        let comment = Comment()
        comment.content = content
        comment.authorName = authorName
        comment.authorEmail = authorEmail
        comment.createdAt = Date()
        
        try await MainActor.run {
            try realm.write {
                post.comments.append(comment)
            }
        }
    }
    
    // ユーザー投稿統計
    func getUserPostStatistics() -> [(user: User, postCount: Int, publishedCount: Int)] {
        let users = realm.objects(User.self)
        
        return users.compactMap { user in
            let totalPosts = user.posts.count
            let publishedPosts = user.posts.filter("isPublished == true").count
            
            guard totalPosts > 0 else { return nil }
            
            return (user: user, postCount: totalPosts, publishedCount: publishedPosts)
        }.sorted { $0.postCount > $1.postCount }
    }
}

// SwiftUIでのリアクティブ更新例
struct PostListView: View {
    @ObservedResults(Post.self,
                     filter: NSPredicate(format: "isPublished == true"),
                     sortDescriptor: SortDescriptor(keyPath: "publishedAt", ascending: false)
    ) var posts
    
    @StateObject private var postRepository = try! PostRepository()
    @State private var selectedCategory: Category?
    @State private var searchText = ""
    
    var filteredPosts: Results<Post> {
        if searchText.isEmpty && selectedCategory == nil {
            return posts
        }
        
        return postRepository.searchPosts(
            titlePattern: searchText.isEmpty ? nil : searchText,
            category: selectedCategory,
            publishedOnly: true
        )
    }
    
    var body: some View {
        NavigationView {
            VStack {
                // 検索とフィルター
                VStack {
                    TextField("Search posts...", text: $searchText)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                    
                    // カテゴリ選択 (簡略化)
                    Text("Category filter would go here")
                }
                .padding()
                
                // 投稿リスト
                List {
                    ForEach(filteredPosts, id: \.id) { post in
                        PostRowView(post: post)
                            .onTapGesture {
                                Task {
                                    try await postRepository.incrementViewCount(post)
                                }
                            }
                    }
                }
            }
            .navigationTitle("Posts")
        }
    }
}

struct PostRowView: View {
    let post: Post
    
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(post.title)
                .font(.headline)
                .lineLimit(2)
            
            Text(post.excerpt)
                .font(.subheadline)
                .foregroundColor(.secondary)
                .lineLimit(3)
            
            HStack {
                if let author = post.author {
                    Text("by \(author.name)")
                        .font(.caption)
                        .foregroundColor(.secondary)
                }
                
                Spacer()
                
                Text("\(post.viewCount) views")
                    .font(.caption)
                    .foregroundColor(.secondary)
                
                if let publishedAt = post.publishedAt {
                    Text(publishedAt, style: .date)
                        .font(.caption)
                        .foregroundColor(.secondary)
                }
            }
            
            // タグ表示
            if !post.tags.isEmpty {
                ScrollView(.horizontal, showsIndicators: false) {
                    HStack {
                        ForEach(Array(post.tags), id: \.self) { tag in
                            Text(tag)
                                .font(.caption)
                                .padding(.horizontal, 8)
                                .padding(.vertical, 4)
                                .background(Color.blue.opacity(0.1))
                                .foregroundColor(.blue)
                                .cornerRadius(8)
                        }
                    }
                    .padding(.horizontal)
                }
            }
        }
        .padding(.vertical, 4)
    }
}

非同期処理とリアクティブプログラミング

import RealmSwift
import Combine

// リアクティブなデータサービス
class RealtimeDataService: ObservableObject {
    private let realm: Realm
    private var notificationTokens: [NotificationToken] = []
    
    @Published var users: Results<User>?
    @Published var posts: Results<Post>?
    @Published var userStats: (total: Int, active: Int, average: Double) = (0, 0, 0.0)
    @Published var postStats: (total: Int, published: Int, drafts: Int) = (0, 0, 0)
    
    init() throws {
        let encryptionKey = RealmEncryptionKeyManager.getOrCreateEncryptionKey()
        let config = Realm.Configuration(encryptionKey: encryptionKey)
        self.realm = try Realm(configuration: config)
        
        setupRealtimeObservers()
    }
    
    deinit {
        notificationTokens.forEach { $0.invalidate() }
    }
    
    private func setupRealtimeObservers() {
        // ユーザーのリアルタイム更新
        let userResults = realm.objects(User.self).sorted(byKeyPath: "createdAt", ascending: false)
        let userToken = userResults.observe { [weak self] changes in
            switch changes {
            case .initial(let results):
                self?.users = results
                self?.updateUserStats()
            case .update(let results, _, _, _):
                self?.users = results
                self?.updateUserStats()
            case .error(let error):
                print("User observation error: \(error)")
            }
        }
        notificationTokens.append(userToken)
        
        // 投稿のリアルタイム更新
        let postResults = realm.objects(Post.self).sorted(byKeyPath: "createdAt", ascending: false)
        let postToken = postResults.observe { [weak self] changes in
            switch changes {
            case .initial(let results):
                self?.posts = results
                self?.updatePostStats()
            case .update(let results, _, _, _):
                self?.posts = results
                self?.updatePostStats()
            case .error(let error):
                print("Post observation error: \(error)")
            }
        }
        notificationTokens.append(postToken)
    }
    
    private func updateUserStats() {
        guard let users = users else { return }
        
        let activeUsers = users.filter("isActive == true")
        let averageAge = users.average(ofProperty: "age") ?? 0.0
        
        DispatchQueue.main.async {
            self.userStats = (
                total: users.count,
                active: activeUsers.count,
                average: averageAge
            )
        }
    }
    
    private func updatePostStats() {
        guard let posts = posts else { return }
        
        let publishedPosts = posts.filter("isPublished == true")
        let draftPosts = posts.filter("isPublished == false")
        
        DispatchQueue.main.async {
            self.postStats = (
                total: posts.count,
                published: publishedPosts.count,
                drafts: draftPosts.count
            )
        }
    }
    
    // 非同期データ操作
    func performBatchUserUpdate(updates: [(User, String, String, Int)]) async throws {
        try await withThrowingTaskGroup(of: Void.self) { group in
            for (user, name, email, age) in updates {
                group.addTask {
                    try await self.updateUserAsync(user, name: name, email: email, age: age)
                }
            }
            
            try await group.waitForAll()
        }
    }
    
    private func updateUserAsync(_ user: User, name: String, email: String, age: Int) async throws {
        try await MainActor.run {
            try realm.write {
                user.name = name
                user.email = email
                user.age = age
                user.updatedAt = Date()
            }
        }
    }
    
    // データクリーンアップ
    func cleanupOldData(olderThanDays days: Int) async throws {
        let cutoffDate = Calendar.current.date(byAdding: .day, value: -days, to: Date()) ?? Date()
        
        try await MainActor.run {
            try realm.write {
                // 古い非公開投稿を削除
                let oldDrafts = realm.objects(Post.self)
                    .filter("isPublished == false AND createdAt < %@", cutoffDate)
                
                realm.delete(oldDrafts)
                
                // 非アクティブユーザーの削除
                let inactiveUsers = realm.objects(User.self)
                    .filter("isActive == false AND updatedAt < %@", cutoffDate)
                
                realm.delete(inactiveUsers)
            }
        }
    }
    
    // エクスポート機能
    func exportUserData() async throws -> [String: Any] {
        let users = realm.objects(User.self)
        let posts = realm.objects(Post.self)
        
        let userData = users.map { user in
            [
                "id": user.id.stringValue,
                "name": user.name,
                "email": user.email,
                "age": user.age,
                "isActive": user.isActive,
                "postCount": user.posts.count
            ]
        }
        
        let postData = posts.map { post in
            [
                "id": post.id.stringValue,
                "title": post.title,
                "isPublished": post.isPublished,
                "viewCount": post.viewCount,
                "authorName": post.author?.name ?? ""
            ]
        }
        
        return [
            "users": userData,
            "posts": postData,
            "exportDate": Date().ISO8601String(),
            "totalUsers": users.count,
            "totalPosts": posts.count
        ]
    }
}

// リアルタイムダッシュボードView
struct RealtimeDashboardView: View {
    @StateObject private var dataService = try! RealtimeDataService()
    @State private var isExporting = false
    @State private var exportData: [String: Any]?
    
    var body: some View {
        NavigationView {
            ScrollView {
                VStack(spacing: 20) {
                    // ユーザー統計
                    VStack(alignment: .leading, spacing: 10) {
                        Text("User Statistics")
                            .font(.headline)
                        
                        HStack {
                            StatCard(title: "Total Users", value: "\(dataService.userStats.total)")
                            StatCard(title: "Active Users", value: "\(dataService.userStats.active)")
                            StatCard(title: "Avg Age", value: String(format: "%.1f", dataService.userStats.average))
                        }
                    }
                    
                    // 投稿統計
                    VStack(alignment: .leading, spacing: 10) {
                        Text("Post Statistics")
                            .font(.headline)
                        
                        HStack {
                            StatCard(title: "Total Posts", value: "\(dataService.postStats.total)")
                            StatCard(title: "Published", value: "\(dataService.postStats.published)")
                            StatCard(title: "Drafts", value: "\(dataService.postStats.drafts)")
                        }
                    }
                    
                    // エクスポートボタン
                    Button("Export Data") {
                        Task {
                            isExporting = true
                            do {
                                exportData = try await dataService.exportUserData()
                                print("Export completed: \(exportData?.keys.joined(separator: ", ") ?? "Unknown")")
                            } catch {
                                print("Export failed: \(error)")
                            }
                            isExporting = false
                        }
                    }
                    .disabled(isExporting)
                    
                    // クリーンアップボタン
                    Button("Cleanup Old Data") {
                        Task {
                            try await dataService.cleanupOldData(olderThanDays: 30)
                        }
                    }
                    .foregroundColor(.red)
                    
                    Spacer()
                }
                .padding()
            }
            .navigationTitle("Dashboard")
            .refreshable {
                // プルトゥリフレッシュは自動で更新されるため特に処理不要
            }
        }
    }
}

struct StatCard: View {
    let title: String
    let value: String
    
    var body: some View {
        VStack {
            Text(value)
                .font(.title2)
                .fontWeight(.bold)
            Text(title)
                .font(.caption)
                .foregroundColor(.secondary)
        }
        .frame(maxWidth: .infinity)
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(8)
    }
}

エラーハンドリングとセキュリティ

import RealmSwift

// カスタムエラー定義
enum RealmError: Error, LocalizedError {
    case initializationFailed(String)
    case encryptionKeyError
    case writeTransactionFailed(String)
    case objectNotFound(String)
    case validationFailed(String)
    case permissionDenied
    case databaseCorrupted
    
    var errorDescription: String? {
        switch self {
        case .initializationFailed(let message):
            return "Database initialization failed: \(message)"
        case .encryptionKeyError:
            return "Encryption key generation or retrieval failed"
        case .writeTransactionFailed(let message):
            return "Write transaction failed: \(message)"
        case .objectNotFound(let message):
            return "Object not found: \(message)"
        case .validationFailed(let message):
            return "Validation failed: \(message)"
        case .permissionDenied:
            return "Permission denied for database operation"
        case .databaseCorrupted:
            return "Database file is corrupted"
        }
    }
}

// 安全なRealm操作クラス
class SecureRealmManager: ObservableObject {
    private let realm: Realm
    private let encryptionKey: Data
    
    init() throws {
        do {
            self.encryptionKey = RealmEncryptionKeyManager.getOrCreateEncryptionKey()
            
            let config = Realm.Configuration(
                encryptionKey: encryptionKey,
                schemaVersion: 2,
                migrationBlock: { migration, oldSchemaVersion in
                    // 安全なマイグレーション処理
                    Self.performSafeMigration(migration: migration, from: oldSchemaVersion)
                },
                shouldCompactOnLaunch: { totalBytes, usedBytes in
                    // 10MB以上かつ使用率50%以下の場合にコンパクト実行
                    let tenMB = 10 * 1024 * 1024
                    return (totalBytes > tenMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5
                }
            )
            
            self.realm = try Realm(configuration: config)
        } catch {
            throw RealmError.initializationFailed(error.localizedDescription)
        }
    }
    
    private static func performSafeMigration(migration: Migration, from oldSchemaVersion: UInt64) {
        if oldSchemaVersion < 1 {
            migration.enumerateObjects(ofType: User.className()) { oldObject, newObject in
                newObject?["updatedAt"] = Date()
            }
        }
        
        if oldSchemaVersion < 2 {
            migration.enumerateObjects(ofType: Post.className()) { oldObject, newObject in
                newObject?["excerpt"] = ""
                newObject?["viewCount"] = 0
            }
        }
    }
    
    // 安全なオブジェクト作成
    func createObjectSafely<T: Object>(_ type: T.Type, configuration: (T) throws -> Void) async throws -> T {
        let object = type.init()
        
        do {
            try configuration(object)
            try validateObject(object)
            
            return try await MainActor.run {
                try realm.write {
                    realm.add(object)
                    return object
                }
            }
        } catch {
            throw RealmError.writeTransactionFailed(error.localizedDescription)
        }
    }
    
    // オブジェクト検証
    private func validateObject<T: Object>(_ object: T) throws {
        if let user = object as? User {
            try validateUser(user)
        } else if let post = object as? Post {
            try validatePost(post)
        }
    }
    
    private func validateUser(_ user: User) throws {
        guard !user.name.isEmpty else {
            throw RealmError.validationFailed("User name cannot be empty")
        }
        
        guard user.email.contains("@") && user.email.contains(".") else {
            throw RealmError.validationFailed("Invalid email format")
        }
        
        guard user.age >= 0 && user.age <= 150 else {
            throw RealmError.validationFailed("Age must be between 0 and 150")
        }
        
        // メール重複チェック
        let existingUser = realm.objects(User.self).filter("email == %@", user.email).first
        if let existing = existingUser, existing.id != user.id {
            throw RealmError.validationFailed("Email already exists")
        }
    }
    
    private func validatePost(_ post: Post) throws {
        guard !post.title.isEmpty else {
            throw RealmError.validationFailed("Post title cannot be empty")
        }
        
        guard post.content.count >= 10 else {
            throw RealmError.validationFailed("Post content must be at least 10 characters")
        }
        
        guard post.author != nil else {
            throw RealmError.validationFailed("Post must have an author")
        }
    }
    
    // 安全な更新操作
    func updateObjectSafely<T: Object>(_ object: T, updates: @escaping (T) throws -> Void) async throws {
        do {
            try await MainActor.run {
                try realm.write {
                    try updates(object)
                    try validateObject(object)
                }
            }
        } catch {
            throw RealmError.writeTransactionFailed(error.localizedDescription)
        }
    }
    
    // 安全な削除操作
    func deleteObjectSafely<T: Object>(_ object: T) async throws {
        try await MainActor.run {
            do {
                try realm.write {
                    realm.delete(object)
                }
            } catch {
                throw RealmError.writeTransactionFailed(error.localizedDescription)
            }
        }
    }
    
    // バックアップ作成
    func createBackup() async throws -> URL {
        let backupURL = getBackupURL()
        
        try await MainActor.run {
            try realm.writeCopy(toFile: backupURL, encryptionKey: encryptionKey)
        }
        
        return backupURL
    }
    
    // バックアップから復元
    func restoreFromBackup(backupURL: URL) async throws {
        guard FileManager.default.fileExists(atPath: backupURL.path) else {
            throw RealmError.objectNotFound("Backup file not found")
        }
        
        // 新しいRealm設定でバックアップから復元
        let restoreConfig = Realm.Configuration(
            fileURL: backupURL,
            encryptionKey: encryptionKey,
            readOnly: true
        )
        
        let backupRealm = try Realm(configuration: restoreConfig)
        
        try await MainActor.run {
            try realm.write {
                // データクリア
                realm.deleteAll()
                
                // バックアップからデータ復元
                let users = backupRealm.objects(User.self)
                let posts = backupRealm.objects(Post.self)
                let categories = backupRealm.objects(Category.self)
                
                realm.add(users, update: .modified)
                realm.add(categories, update: .modified)
                realm.add(posts, update: .modified)
            }
        }
    }
    
    // データベース整合性チェック
    func performIntegrityCheck() -> [String] {
        var issues: [String] = []
        
        // 孤立投稿チェック
        let postsWithoutAuthors = realm.objects(Post.self).filter("author == nil")
        if !postsWithoutAuthors.isEmpty {
            issues.append("\(postsWithoutAuthors.count) posts without authors found")
        }
        
        // 重複メールチェック
        let users = realm.objects(User.self)
        let emails = users.map { $0.email }
        let uniqueEmails = Set(emails)
        if emails.count != uniqueEmails.count {
            issues.append("Duplicate email addresses found")
        }
        
        // 無効データチェック
        let invalidUsers = users.filter("age < 0 OR age > 150")
        if !invalidUsers.isEmpty {
            issues.append("\(invalidUsers.count) users with invalid age found")
        }
        
        return issues
    }
    
    // セキュアな削除(データの完全削除)
    func secureDelete() async throws {
        try await MainActor.run {
            try realm.write {
                realm.deleteAll()
            }
        }
        
        // ファイルの物理削除
        let realmURL = realm.configuration.fileURL!
        let realmURLs = [
            realmURL,
            realmURL.appendingPathExtension("lock"),
            realmURL.appendingPathExtension("note"),
            realmURL.appendingPathExtension("management")
        ]
        
        for url in realmURLs {
            try? FileManager.default.removeItem(at: url)
        }
    }
    
    private func getBackupURL() -> URL {
        let documentsPath = FileManager.default.urls(for: .documentDirectory, 
                                                   in: .userDomainMask)[0]
        let timestamp = ISO8601DateFormatter().string(from: Date())
        return documentsPath.appendingPathComponent("realm_backup_\(timestamp).realm")
    }
}

// エラーハンドリング使用例
struct SecureDataView: View {
    @StateObject private var realmManager = try! SecureRealmManager()
    @State private var errorMessage: String?
    @State private var showingError = false
    @State private var isBackingUp = false
    @State private var integrityIssues: [String] = []
    
    var body: some View {
        VStack {
            Button("Create Safe User") {
                Task {
                    await createUserSafely()
                }
            }
            
            Button("Run Integrity Check") {
                integrityIssues = realmManager.performIntegrityCheck()
            }
            
            Button("Create Backup") {
                Task {
                    await createBackup()
                }
            }
            .disabled(isBackingUp)
            
            if !integrityIssues.isEmpty {
                Text("Integrity Issues:")
                    .font(.headline)
                    .foregroundColor(.red)
                
                ForEach(integrityIssues, id: \.self) { issue in
                    Text("• \(issue)")
                        .foregroundColor(.red)
                }
            }
        }
        .alert("Error", isPresented: $showingError) {
            Button("OK") { }
        } message: {
            Text(errorMessage ?? "Unknown error")
        }
    }
    
    private func createUserSafely() async {
        do {
            let user = try await realmManager.createObjectSafely(User.self) { user in
                user.name = "John Doe"
                user.email = "[email protected]"
                user.age = 30
            }
            print("User created safely: \(user.name)")
        } catch {
            errorMessage = error.localizedDescription
            showingError = true
        }
    }
    
    private func createBackup() async {
        isBackingUp = true
        do {
            let backupURL = try await realmManager.createBackup()
            print("Backup created at: \(backupURL)")
        } catch {
            errorMessage = error.localizedDescription
            showingError = true
        }
        isBackingUp = false
    }
}