[Swift] Keychainとセキュアストレージ

スマホに安全に保存したいもの例:
– ログイン時に受け取る アクセストークン
– ユーザーの パスワード
– API の Bearer Token
これらは UserDefaults に保存してはいけない
Keychain に保存する

### Modelsフォルダ
KeychainManager.swift

import Foundation
import Security

class KeychainManager {
    static func save(key: String, value: String) {
        let data = value.data(using: .utf8)!
        
        // 既存があれば削除
        let query = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key
        ] as CFDictionary
        SecItemDelete(query)
        
        // 保存
        let attributes = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key,
            kSecValueData: data
        ] as CFDictionary
        
        SecItemAdd(attributes, nil)
    }
    
    static func load(key: String) -> String? {
        let query = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key,
            kSecReturnData: true,
            kSecMatchLimit: kSecMatchLimitOne
        ] as CFDictionary
        
        var result: AnyObject?
        SecItemCopyMatching(query, &result)
        
        if let data = result as? Data {
            return String(data: data, encoding: .utf8)
        }
        return nil
    }
    
    static func delete(key: String) {
        let query = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key
        ] as CFDictionary
        
        SecItemDelete(query)
    }
}

ContentView.swift

import SwiftUI

struct ContentView: View {
    @State private var tokenInput = ""
    @State private var storedToken = ""
    
    var body: some View {
        VStack(spacing: 20) {
            TextField("トークンを入力", text: $tokenInput)
                .textFieldStyle(.roundedBorder)
                .padding()
            
            Button("保存") {
                KeychainManager.save(key: "myToken", value: tokenInput)
            }
            .buttonStyle(.borderedProminent)
            
            Button("読み込み") {
                storedToken = KeychainManager.load(key: "myToken") ?? "(なし)"
            }
            
            Text("保存されているトークン:\(storedToken)")
                .padding()
            
            Button("削除") {
                KeychainManager.delete(key: "myToken")
                storedToken = "(削除されました)"
            }
            .foregroundColor(.red)
        }
        .padding()
    }
}

ほう、なるほど