モノトーンの伝説日記

Apex Legends, Splatoon, Programming, and so on...

<mini> Keychain の落とし穴

 Keychain でテストコード書いてたら、めっちゃ上手くいかなくて、落とし穴結構ハマりましたw

1. kSecClass によって保持できる値が違う

 dictionary 型で割と自由に指定できると思ってたら、そんなことはなく結構厳格にデータ見ているみたいです。したがって、実装するときは、公式ドキュメントの Item Class Values の個々のページを見てください。

developer.apple.com

2. データだけ欲しい場合、CFDictionary 型ではなく、CFData 型で帰ってくる。

 テスト書いてるとき、コード何度も見直したけど、一見間違いはなさそうに見えて実は CFData が実態でしたというお話。

private func throwIfError(_ status: OSStatus) throws {
#if DEBUG
  guard status != errSecMissingEntitlement else { throw SecureStorageError.missingEntitlement }
#endif
  guard status == errSecSuccess else { throw SecureStorageError.unhandledError(status) }
}

func getAccessToken(_ service: ServiceType, uuid: UUID) throws -> String? {
  let query: [CFString: Any] = [
    kSecClass: kSecClassGenericPassword as String,
    kSecAttrAccount: uuid.uuidString,
    kSecAttrService: service.description,
    kSecMatchLimit: kSecMatchLimitOne as String,
    kSecReturnData: true,
  ]

  var item: CFTypeRef?
  let status = SecItemCopyMatching(query as CFDictionary, &items)
  guard status != errSecItemNotFound else { return nil }
  try throwIfError(status)

  guard let data = item as? Data else { // ここ as? [CFString: Any] ではない
    return nil
  }
  let accessToken = String(data: data, encoding: .utf8)!
  return accessToken
}

3. watchOS では iCloud で同期できない

 これは事前に調べて知っていたことなのですが、できないので、できると思ってデバッグしないように! 程度かな。

notes.tret.jp

 でもスタンドアローンで実行するようになっていくにはこれもいい加減対応して欲しいですね。

4. Simulator では Access Group は test 固定

cocoadays.blogspot.com

5. エラーは OSStatus.com で

 いいサイトですね。

www.osstatus.com

最後に

 比較的早くテスト書き終わってよかった……

 自分は基本、この程度の軽い中間層は自前で書くのですが、落とし穴が多すぎてコード修正に時間がかかったという話でした。まあ、一度実装してしまえば難しくないのと、全クエリーを定義していたとしても、200 行以下で行けるんで、これぐらいなら書いてしまったほうが保守管理楽ですね。

 将来的に Windows 移植 (Swift 自体はもう Windows でも動くので、あとは周辺の GUI Framework とかは必要でしょうけど) とかなると、この辺り自前で Repository パターンとかで置き換えられる方が後々楽かな、って思ったりします。あとはテスト用に OnMemoryStorage とか作るときとか。