技術ブログ

プログラミング、IT関連の記事中心

KeyChainの操作(CRUD)の方法【Swift】

■はじめに

アプリ内のデータを保存する際にUserDefaultsなど様々な方法で保存ができます。
ここで紹介するKeyChainへの保存は、データを強固に保存することができます。
また、同じAppleIDを利用する事で別端末へ引き継いだりもできます。

■手順

KeyChainの操作はゴチャゴチャしていてわかりづらいので、共通定義を用意しました。
「KeyChain.swift」を作成して以下を丸々コピペしてください。

import Foundation

/// 継承不可のKeyChain操作クラス
struct KeyChain {
    // MARK: - 保存 / 更新
    
    /// String型のデータの保存処理
    /// - Parameters:
    ///   - key: 保存する対象のキー
    ///   - value: 保存する対象のString型の値
    static func setKeyChain(key: String, value: String) {
        guard let data = value.data(using: .utf8) else { return }
        setKeyChainCommon(key: key, data: data)
    }
    
    /// Int型のデータの保存処理
    /// - Parameters:
    ///   - key: 保存する対象のキー
    ///   - value: 保存する対象のInt型の値
    static func setKeyChain(key: String, value: Int) {
        setKeyChain(key: key, value: String(value))
    }
    
    /// Double型のデータの保存処理
    /// - Parameters:
    ///   - key: 保存する対象のキー
    ///   - value: 保存する対象のDouble型の値
    static func setKeyChain(key: String, value: Double) {
        setKeyChain(key: key, value: String(value))
    }
    
    /// Float型のデータの保存処理
    /// - Parameters:
    ///   - key: 保存する対象のキー
    ///   - value: 保存する対象のFloat型の値
    static func setKeyChain(key: String, value: Float) {
        setKeyChain(key: key, value: String(value))
    }
    
    /// Bool型のデータの保存処理
    /// - Parameters:
    ///   - key: 保存する対象のキー
    ///   - value: 保存する対象のBool型の値
    static func setKeyChain(key: String, value: Bool) {
        setKeyChain(key: key, value: String(value))
    }
    
    /// Any型のデータの保存処理(配列や辞書などの関数が用意されていないものはこの関数を利用する)
    /// - Parameters:
    ///   - key: 保存する対象のキー
    ///   - value: 保存する対象のAny型の値
    static func setKeyChain(key: String, value: Any) {
        if let archiveData = try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true) {
            setKeyChainCommon(key: key, data: archiveData)
        }
    }
    
    /// データの保存共通処理
    /// - Parameters:
    ///   - key: 保存する対象のキー
    ///   - data: 保存するデータのData型の値
    fileprivate static func setKeyChainCommon(key: String, data: Data) {
        deleteKeyChain(key: key)
        SecItemAdd([kSecClass: kSecClassGenericPassword,
                    kSecValueData: data,
                    kSecAttrAccount: Bundle.main.bundleIdentifier ?? "",
                    kSecAttrService: key] as CFDictionary, nil)
    }

    // MARK: - 取得
    
    /// String型のデータ取得処理
    /// - Parameter key: 取得データのキー
    static func getStringKeyChain(key: String) -> String? {
        guard let targetData = getKeyChainCommon(key: key) else { return nil }
        return String(data: targetData, encoding: .utf8)
    }
    
    /// Int型のデータ取得処理
    /// - Parameter key: 取得データのキー
    static func getIntKeyChain(key: String) -> Int? {
        return Int(getStringKeyChain(key: key) ?? "")
    }
    
    /// Double型のデータ取得処理
    /// - Parameter key: 取得データのキー
    static func getDoubleKeyChain(key: String) -> Double? {
        return Double(getStringKeyChain(key: key) ?? "")
    }
    
    /// Float型のデータ取得処理
    /// - Parameter key: 取得データのキー
    static func getFloatKeyChain(key: String) -> Float? {
        return Float(getStringKeyChain(key: key) ?? "")
    }
    
    /// Bool型のデータ取得処理
    /// - Parameter key: 取得データのキー
    static func getBoolKeyChain(key: String) -> Bool? {
        return Bool(getStringKeyChain(key: key) ?? "")
    }
    
    /// Any型のデータ取得処理(配列や辞書などの関数が用意されていないものはこの関数を利用する)
    /// - Parameter key: 取得データのキー
    static func getAnyKeyChain(key: String) -> Any? {
        guard let data: Data = getKeyChainCommon(key: key) else { return nil }
        return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
    }
    
    /// データ取得処理の共通処理
    /// - Parameter key: 取得データのキー
    fileprivate static func getKeyChainCommon(key: String) -> Data? {
        var queryResult: AnyObject?

        _ = withUnsafeMutablePointer(to: &queryResult) {
            SecItemCopyMatching([kSecClass: kSecClassGenericPassword,
                                          kSecAttrAccount: Bundle.main.bundleIdentifier ?? "",
                                          kSecAttrService: key,
                                          kSecReturnAttributes: kCFBooleanTrue as Any,
                                          kSecMatchLimit: kSecMatchLimitOne] as CFDictionary, UnsafeMutablePointer($0))
        }
        
        // パスワード以外のキーを取得する
        guard var valueQuery = queryResult as? [AnyHashable: AnyHashable] else { return nil }
        valueQuery[kSecClass] = kSecClassGenericPassword
        valueQuery[kSecReturnData] = kCFBooleanTrue

        // パスワードを取得する
        var valueQueryResult: AnyObject?

        _ = withUnsafeMutablePointer(to: &valueQueryResult){
            SecItemCopyMatching(valueQuery as CFDictionary, UnsafeMutablePointer($0))
        }
        
        return valueQueryResult as? Data
    }

    // MARK: - 削除
    
    /// 削除処理
    /// - Parameter key: 削除対象のキー
    static func deleteKeyChain(key: String) {
        SecItemDelete([kSecClass: kSecClassGenericPassword,
                       kSecAttrAccount: Bundle.main.bundleIdentifier ?? "",
                       kSecAttrService: key] as CFDictionary)
    }
}

これで、簡単にKeyChainを使用する準備ができました。
以下の様に使用する事で、KeyChainにデータを保存することができます。
※以下では、「value」をString型で記載していますが、その他のデータ型でも同様です。
※「key」に関してはString型で定義する必要があります。
※既に保存されているKeyの場合には、データが上書きされます。(この動きでデータを更新します。)

KeyChain.setKeyChain(key: "key", value: "VALUE")

以下の様に使用する事で、KeyChainからデータを取得することができます。
※データ型によって、取得方法を変更する必要があるので、メソッド名は「get[データ型]KeyChain」の様にしてください。
※「key」に関してはString型で定義する必要があります。
※配列型や辞書型などは「Any」で取得した後にキャストする必要があります。

KeyChain.getStringKeyChain(key: "key")

以下の様に使用する事で、KeyChainからデータを削除することができます。
※「key」に関してはString型で定義する必要があります。

KeyChain.deleteKeyChain(key: "key")