月: 2025年10月

  • 開発中のmacOS アプリでKeychain への書き込みが-24299 エラーとなる

    急に発生し、調べても調べても解決できずに困ってしまった。
    AI の回答は、App Sandbox やentitlements に問題があるというものだったが、いずれも該当せずでした
    結論をいうと、アプリのBundle Identifier が変更されたため、別のアプリと扱われたことが原因だった。

    再発手順

    • 古いアプリでKeychain に書き込み
    • アプリを更新(Bundle Identifier を変更)
    • 新しいアプリで1. と同じキーの値を更新しようとすると –> エラー発生

    確認手順

    • Mac のKeychain を起動する
    • デフォルトキーチェーン: ログイン > パスワードを選択
    • 変更日で並び替えると探しやすい
    • アプリで使用しているキーを一覧から探してクリックする
    • アクセス制御タブを表示する

    こちらのアプリが同一でないと、Keychain の値を更新・削除できません。

    対応案

    Mac の「キーチェーンアクセス」アプリから対象のキーを削除すると、アプリから再度書き込みできます。キー名称を右クリックすると削除できます。

    開発中にBuneld Identifier を変更する際はご注意ください。(あまりないかもですが)

    【広告】

  • 端末間のデータ同期にwebsocket とポーリングのどちらを使うべきか

    端末間でデータを同期させたいとき、選択肢として「WebSocket」と「ポーリング」があります。リアルタイム性をどこまで求めるのかによりますが、私の場合リアルタイム性を少し捨ててポーリングを選びました。検討中に気になったポイントをまとめておきます。

    WebSocket

    WebSocket はサーバとクライアントの双方向通信が可能な仕組みです。ただし、通知が相手に確実に届いたかどうかはWebSocket の仕組みでは保証されないため、必要であればアプリ側受信確認やリトライの実装が必要になります。
    また、スマホの場合はトンネルなどで通信断となった場合に、スマホへリアルタイム通知が届かない可能性があります。

    ポーリング

    30秒に一度など定期的にサーバへ更新が入っていないか問い合わせを行う方式です。WebSocket に比べてリアルタイム性は落ちてしまいます。
    スマホから接続するため、通信断についてはスマホからリトライを行うだけでよく、運用が楽になります。
    ただし、WebSocket に比べて通信量が多くなります。ポーリングは毎回新たなHTTP リクエストを発行するため、アクセストークンやヘッダーが送信されます。1回のポーリングで数百バイト〜数KB程度の通信が発生し、これらがネットワークやサーバーの負荷となります。

    結論

    websocket を導入したとしても、スマホの通信断から復旧時にポーリングを行う必要があると考えました。
    それであれば、多少のリアルタイム性を捨ててポーリングに一本化することで、開発と運用のコストをさげられるポーリングの方が現実的と思いました。

    【広告】

  • Spring Boot + Spring Security + Keycloak でJWT の公開鍵を更新してみる

    私は、JWT 署名に使われる公開鍵を変更した場合、リソースサーバで新しい公開鍵を取得する作業が必要だと考えていました。
    しかし、結論から言うとこれは間違いでした。Spring Boot + Spring Security を使用している場合は何もせずに自動対応されます。

    本当にそうなのか、少し不安に思い検証してみました。

    Keycloak で公開鍵の追加

    Keycloak に公開鍵を追加して試してみます。

    Realm settings > Keys
    ここで鍵の一覧を確認できます。JWT の署名で必要なのはRS256 になります。既存の鍵が1つ存在します。

    ※画像は Keycloak バージョン 26.1.4 のものです。以下同様です。

    公開鍵を追加する場合、Add providers > Add provider をクリックします。

    鍵の種類を選ぶ画面が出ますので、rsa-generated を選択します。

    既存の鍵と区別するためにName をrsa-generated-test とします。

    一覧画面で追加されていることが確認できます。

    この状態で、JWKS エンドポイントをブラウザで表示してみます。

    https://<KEYCLOAK_HOST>/realms/<REALM_NAME>/protocol/openid-connect/certs

    表示されたJSON をchrome console で解析させると、kid が8wY… から始まる鍵が公開されていることが分かります。

    古い鍵のprovider のEnabled をoff に変更すると、JWKS エンドポイントからも削除されます。

    Spring Boot で検証

    以下の順序で検証してみました。

    • Spring boot を起動
    • Keycloak に新しい公開鍵の追加して、古い鍵を無効化する
    • 新しい鍵で著名されたJWT をSpring boot に送信

    結果、Spring boot を再起動することなく、新しい鍵によるJWT 検証が行われました。

    内部の挙動(推測)

    Spring boot はJWK を一定時間キャッシュします。
    JWT ヘッダーに含まれるkid に対応する公開鍵がキャッシュに存在しない場合、JWKS エンドポイントを呼び出して新たな鍵を取得した上で再度検証を行なっていると考えられます。

    便利ですね。

    【広告】

  • バックグラウンド処理から@Published 付き変数を更新する(swift)

    このエラーに悩んだ。

    Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.

    XCode を探すと紫色の警告が出ていた。(探さないと分からなかった)


    どうやら、バックグラウンドの処理で、@Published がついた変数を変更するとこのエラーが出るようだ。

    私が開発中のアプリは、アプリ起動直後にローディング画面を表示し、そのバックグラウンドで初期化処理を行なっている。
    バックグラウンドから@Published 付き変数を無邪気に更新すると、上のエラーが出る。

    バックグラウンドスレッドの作り方

    そもそもだが、メインスレッドを使わず、バックグラウンドスレッドを作るには下のように書く。

    Task を使う例

    Task {
      await initMethodA()
    }


    async let :() を使う例

    async let initB: () = try initMethodB()
    async let initC: () = try initMethodC()
    _ = try await (initB, initC)

    ※initMethodB とinitMethodC が並列実行される

    そして、バックグラウンドスレッドから以下のように変数を更新すると上のエラーになる。

    func initMethodA() async throws {
      someVM.publishedValue = "newValue"
    }

    バックグラウンドスレッドからMainActor で実行させる書き方

    私の調査では2つのやり方が見つかった。

    MainActor.run を使うやり方

    func initMethodA() async throws {
      await MainActor.run {
        someVM.publishedValue = "newValue"
      }
    }

    MainActor.run の内側はメインスレッドで実行される。
    メインスレッドで変数を更新すればエラーは起きない。

    気をつけなければならないのは、await を使うことができない点だ。
    使いたい場合は、先にawait が必要な処理を行っておくとよい。

    func initMethodA() async throws {
      // 先に非同期処理を実行
      let newValue = await someAsyncMethod()
      await MainActor.run {
        // メインスレッドは変数の代入のみ
        someVM.publishedValue = newValue
      }
    }

    Task { @MainActor in を使うやり方

    Task { @MainActor in
      someVM.publishedValue = await someAsyncMethod()
    }

    もう一つがこちら。await が使えるので使いやすい。コードも簡潔で見通しも良い。

    しかし、Task がメインスレッドで実行されて、someAsyncMethod はメインか別スレッドのどちらかで実行されて、publishedValue への代入はメインスレッドで実行される。
    MainActor.run に比べて、メインスレッドの出番が多い。

    私が思う解決策

    @Published にprivate(set) を追加することで、外部から変更不可にできる。代わりに、ViewModel にsetter を作成してsetter の中でMainActor.run を使うことで、呼び出し元がメインスレッドかどうかを考えずに済む。

    class SomeViewModel: ObservableObject {
      @Published private(set) var publishedValue = ""
      func setValue(_ newValue: String) async {
        await MainActor.run {
          publishedValue = newValue
        }
      }
    }

    残念だがこれは完璧ではない。画面から更新する変数の場合、private(set) を付けられないことがある。例えばトグルの場合だ。

    Toggle("有効にする",isOn: $someVM.publishedValue)

    ※publishedValue はView から更新する必要がある


    このようなケースは仕方ないので、個別に対応するしかない。(と思う)

    【広告】