tech
技術記事
-
社内向けiOSアプリをApp Storeで配信する手順
はじめに 社内エンジニアの技術領域を広げる取り組みの一環として、普段、Web開発を担当している
エンジニアが iPhone / Apple Watch向けの「打刻アプリ」の開発にチャレンジしました。
弊社では勤怠管理はある勤怠管理サービスを利用しています。
公式アプリやWeb打刻画面は用意されているものの、日々の出退勤の打刻としては
ステップ数が多いため、ワンタップで打刻とSlack通知を完結できる補助アプリを開発しました。
このアプリは社内の人しか使わないのですが、配布方法としてはApp Storeに載せたいと考えました。
理由は後述しますが、ざっくり言うと 「普通のアプリと同じようにインストールとアップデートをしたかった」 からです。
ただ、一般公開はしたくないので、 Unlisted App(非表示アプリ)配信 という仕組みを利用しました。
非表示アプリの配信自体は弊社でも活用実績がありますが、Webエンジニアが実際に申請から
リリースまで 進めることで 手順の落とし穴や改めて整理できた知見が多くありました。
そこでこの記事では、 そもそもUnlisted Appとは何なのか 手順どおりに進めたつもりがリジェクトされた理由 実際に必要だった申請方法と申請時に何を書いたか 承認されたあとの運用とTips を実際にやった順番で書いていきます。 「社内ツールをApp Storeで配りたいけど公開はしたくない」という方の参考になれば嬉しいです。
(※以下の内容は2026年8月時点のものです。Apple側の手順や仕様は今後変わる可能性があります) Unlisted App(非表示アプリ)とは はじめに Unlisted App が何なのかを簡単に説明しておきます。
Unlisted App(App Store Connect上では「非表示アプリ」と表記されています。
以下、この記事では「Unlisted App」「Unlisted配信」を同じ意味で使います)は
App Storeに配信されるけれど、検索結果やランキング、カテゴリ一覧などには一切出てこないアプリ のことです。
配布は「直接リンク(App StoreのURL)」経由でのみ行われます。
つまり、URLを知っている人だけがそのアプリのページにたどり着き、インストールすることができます。 配信方法検索に出るか誰がインストールできるか備考通常の公開配信出る誰でも一般消費者向けUnlisted App出ないリンクを知っている人社内ツール・限定配布向けApple Business Manager経由のカスタムApp配布出ない組織が管理する範囲ABM契約や管理デバイスの登録が前提TestFlight出ないテスターとして招待した人ビルドに有効期限あり(最大90日) ポイントは、Unlisted Appでも中身は普通のApp Store配信と同じだ、ということです。 インストールもアップデートも通常のApp Storeの仕組みがそのまま使える TestFlightのようにビルドの有効期限を気にしなくていい 端末やApple IDを事前に登録する必要がない 「不特定多数には見せたくないけど、配布と更新は楽にしたい」というケースに向いている仕組みです。
背景・課題:なぜUnlistedにしたかったのか なぜ一般公開ではダメだったのか、という話です。
今回のアプリは完全に社内向けの業務ツールで、関係のない人が使う意味はありません。
そのアプリを普通にApp Storeで公開すると、こういうことが起きそうだと考えました。 「勤怠」などのワードで検索した無関係な人がダウンロードしてしまう アプリ内に出てくる組織名や勤務区分といった社内用語・業務用語が外部の人の目に触れる 使えるはずのない人からのテスト打刻・問い合わせが発生する 使えない以上、低評価レビューがつきかねない かといって、TestFlightで配り続けるのもビルドの期限管理が面倒ですし、Apple Business Manager(ABM)を
新たに契約して管理体制を整える、というのも社内ツール1本のためにやるには重すぎます。
そこで「Unlisted Appがちょうどいいのでは」となりました。 やったこと1:手順どおりに審査提出して、見事にリジェクトされた
ここからが本題です。
Unlisted配信をするには専用の申請が必要だということ自体は把握していましたが、最新の細かい手順がわかりませんでした。 そこで、参考にしたサイトに書かれていた手順が「まず通常どおり審査に提出し、その際にApp Review担当者向けのメモ(Notes)欄へ
Unlisted配信を希望する旨を記載しておく」というものだったので、そのとおりに進めました。
メモ欄には、こんな趣旨の説明を英語で書いています(以下は日本語訳です)。
このアプリは非表示(Unlisted)で配信予定のアプリです。App Storeの直接リンク経由でのみ利用可能である必要があります。
参考にしたサイトの情報がすでに古くなっていた可能性もありますが、手順どおりなので問題ないだろうと思っていました。 しかし、結果はリジェクトでした。
リジェクト理由:Guideline 3.2 - Business 内容を要約すると、だいたい次のようなことが書かれていました。
このアプリは特定の企業・組織向けの利用を意図しているように見えるのに、App Store Connect上では「公開配信」が選択されている App Storeは一般ユーザー向けの場なので、他の配信オプションを検討してほしい Unlisted App配信の申請を既に出しているなら、結果が来るまで再提出しないこと
つまり、審査提出と同時にメモで希望を伝えるだけでは足りず、
配信形態そのものの承認を先に取っておく必要がある、ということでした。 特に後半、「申請中なら結果が来るまで再提出しないこと」という案内が決定的でした。
審査提出とセットにする必要はなく、申請フォームのほうを先に済ませておけばよかったようです。 やったこと2:必要だったのは「専用フォームでの事前申請」だった
Unlisted App配信の申請は、専用の申請フォームから行います。 Unlisted App配信のリクエストフォーム: https://developer.apple.com/contact/request/unlisted-app/
流れを整理するとこうなります。
進めた流れ(参考にした手順どおり/結果的に遠回り) 通常どおり審査提出(Notes欄にUnlisted希望と記載) Guideline 3.2 - Business でリジェクト 審査提出とセットにする必要はないと気づく 申請フォームから申請 → 承認 あらためて審査提出 実際に必要だった流れ 申請フォームから Unlisted App 配信を申請 承認のメールを待つ 承認されたら通常どおり審査提出
リジェクト文にも「申請中なら結果メールが来るまで再提出しないでね」と書かれているとおり、
まず配信形態の承認を取ってから審査に出すのが正しい順序でした。
審査提出自体はどちらにしても必要ですが、それより前に専用フォームでの申請・承認を
済ませておくべきだった、ということです。
私の場合は、すでに(リジェクト済みの)審査提出をしていた状態でこのフォームから申請しましたが、
それでも問題なく承認されました。 やったこと3:申請フォームで何を書いたか(ポイント)
申請フォームは英語で、アプリ情報と「なぜUnlistedでなければならないのか」を問う設問がいくつか並んでいます。
回答文をそのまま載せてもコピペ用テンプレになってしまうだけなので、ここでは
設問ごとにどういう観点で書くと伝わりやすいかを、実際に書いたときに意識したポイントとしてまとめます。
基本情報の入力 App Name: アプリ名を入力する欄です。 App ID: App Store Connect上のアプリを特定する数値IDの入力が必要です(Bundle IDとは別物なので注意)。
App Store Connectの「対象アプリ → 一般 → アプリ情報」にある「Apple ID」の値がこれにあたります。 審査に提出済みかどうか: 提出済み/未提出を選ぶ設問があります。
「解決したいビジネス課題となぜUnlisted配信が必要か」
ここが一番の山場だと思います。「背景・課題」で書いた内容(社内専用ツールであるため、公開すると起きうる問題)を土台にしつつ、
次の2点を意識して英語でまとめました。抽象的な希望ではなく、公開した場合に具体的に何が困るのかとセットで書くのがポイントです。
利用者が誰なのかを明確にする
自社の社員だけが使うこと。関係のない第三者には使う意味がないツールであること なぜ「Unlisted」がその解決策として適切なのかを結びつける
検索結果に出ないこと、直接リンクを知っている社員だけがインストールできること、
それでいて配布とアップデートは通常のApp Storeの仕組みをそのまま使えること
「なぜ公開配信ではなくUnlisted配信を望むのか」
ここでは一般ユーザーにとって価値がないことに絞って書きました。
業務専用ツールで、一般消費者には使いどころがなく、公開すれば誤インストールや社内情報の露出、
無関係な問い合わせによるサポート負荷増加が想定される、という論旨です。
「なぜ Apple Business Manager / Apple School Manager 経由のカスタムApp配布ではなくUnlistedなのか」
これは「組織限定配布ならABMがあるでしょう」という当然の疑問に答える設問です。
ここはABMが自社の状況に合わない理由を事実ベースで簡潔に書きました。
「ABMを知らないから使わない」ではなく「検討したうえで、規模とコストの観点で合理的でない」という
書き方を意識しました。
小規模組織で、ABM自体を契約していないこと ABM経由の配布は法人確認手続きやデバイスの管理登録が前提になるが、この規模のツール1本のために新たに整備するのはコストに見合わないこと Unlistedであれば追加の登録なしに通常のApp Store経由でインストール・更新できること
選択式の設問 残りは選択肢を選んでいくだけです。私は次のように回答しました。 設問回答社内/社外どちらへの配布か社内のみ(Internally)利用人数◯◯名程度利用組織数1組織(自社のみ)想定利用者像社員カテゴリ勤怠・シフト管理系(Employee logistics)配信先デバイス管理されたデバイス(Managed)提供地域日本 「配信先デバイス」は会社支給の端末という意味合いで「Managed」を選びましたが、本格的なMDMを
運用しているわけではありません。「ABMのような管理基盤を新たに整備するほどではない」という前段の
説明と少しずれる回答でしたが、特に指摘されることもなく、無事に承認されました。 承認後の運用
承認されたあとは、拍子抜けするくらい普通です。 アプリのアップデート時はこれまでどおりApp Store Connectから審査に提出する 承認されれば公開される 社員は通常のApp Storeアプリと同じように更新できる 配信が「非表示」になるだけで、運用フロー自体はほぼ一般公開のアプリと変わりません。
TestFlightのようにビルド期限を気にしたり、テスター追加の手間がなくなったのは
地味ですが、かなり大きいです。 気づいた点・Tips やってみて分かったことをいくつかまとめておきます。 1. 配布用の直接リンクはどこにあるのか
Unlisted Appは検索に出てこないので、社員に共有するための直接リンク(ストアURL)が必要になります。
これは App Store Connect の以下の場所に記載されています。ここに載っているURLを社内に共有すれば
社員はそこからインストールできます。
対象アプリ → 収益化 → 価格および配信状況 → アプリの配信方法 → 「非表示アプリ」の項目
2. 申請に必要な権限は「Admin」でも大丈夫だった
事前に調べていたときは「Unlisted App申請にはApple Developerアカウントのアカウントホルダー
(Account Holder)権限が必要」と書かれている情報をいくつか見かけました。
ただ、実際には Admin権限のアカウントでも申請・手続きができました。もちろん今後変わる
可能性はありますし、環境によっても違うかもしれませんが、「アカウントホルダーの人を
捕まえないと何も進まない」と諦める前に一度Adminで試してみてもいいかもしれません。
3. あとから一般公開に切り替えるのは簡単ではない
Unlisted Appとして公開したあとに「やっぱり一般公開したい」となった場合、
設定をひとつ変えるだけで切り替えられるような仕組みは用意されていないようです。
現実的には、Bundle IDなどを変更してApp Store Connect上で新規にアプリレコードを
作り直すのが早いようです。
「まずは社内限定で、うまくいったら一般公開しよう」という段階的な展開を考えている場合は
この点を頭に入れておいたほうがよさそうです。
最初から公開前提なら、Unlistedを経由しないほうがいいかもしれません。
限定配信の良い点・注意点 良い点 新たにABMやMDMを整備しなくても、通常のApple ID・App Store経由でインストール・アップデートできる TestFlightのようなビルド期限や招待管理から解放される 検索やランキングに出ないので、無関係なユーザーの流入を気にしなくていい 注意点 URLを知っていれば誰でもインストールできるので、アクセス制御にはならない(アプリ側の認証は別途必要) あとから一般公開に切り替えるのは手間がかかる 審査自体は通常どおり行われるので、ガイドライン対応が不要になるわけではない まとめ 今回の内容をまとめるとこうなります。
Unlisted App配信をしたいなら、まず専用フォーム(https://developer.apple.com/contact/request/unlisted-app/)で申請 審査のメモ欄にUnlisted配信を希望する旨を書くこと自体は無駄ではありませんが、
それで配信形態が変わるわけではなく、専用フォームでの申請と承認が別途必要 承認後は通常のApp Store配信とほぼ同じ運用。
配布用の直接リンクはApp Store Connectの「価格および配信状況」から取得できる。
参考にしていた手順どおりに進めたところ一度リジェクトされてしまいましたが、専用フォームからの申請自体は
内容もそこまで難しくありませんでした。申請フォームの自由記述も、「なぜ限定配布が必要か」
「なぜ他の手段(公開配信・ABM)ではダメなのか」を自社の実情に沿って具体的に書けば十分伝わると思います。
テンプレート的な文章をそのまま貼るよりは自分たちの困りごとを素直に書いたほうが説得力があるはずです。
また今回は普段Web開発を担当しているエンジニアが、アプリ開発からストア申請までを一通りやりきったことで
エンジニアとしての技術の幅を広げる良い機会にもなりました。
社内ツールをiPhoneアプリで作りたいけれど配布方法で悩んでいる、という方の参考になれば幸いです。 -
生成AIと作るリアルタイム音声ビジュアライザー (修正編)
はじめに 前回(生成AIと作るリアルタイム音声ビジュアライザー (実装編))では、
実際にCodexにプロンプトを渡して、実機で動作するアプリを作成出来ましたが、
スペクトラムバーの表示に納得行かない部分が発生しました。 今回の記事ではその原因を元に、対応方法を考えCodexに依頼して修正していきます。 前回まとめた原因 FFT(高速フーリエ変換:Fast Fourier Transform)の出力は、
そのまま使用すると非常に変動が大きく、フレームごとに値が大きく上下します。 マイク入力では、 マイク自体のノイズ 空調音 微小な環境音 などもすべて数値として現れるため、
そのまま表示するとスペクトラムが過敏に反応してしまうことがあります。 多くの実装では次のような処理が行われているので、同様の処理をCodexに依頼して修正していきます。 値の平滑化(Smoothing) 微小ノイズの除去(Noise Floor) 表示用の正規化(Normalization) Codexに安定化を依頼する それではCodexに対して表示の安定化を依頼するプロンプトを作成していきます。 ここで重要なのは、AIに対して「全面的な書き直し」を依頼しないことです。 今回のコードはすでに、 マイク入力の取得 FFT解析 SwiftUIによる表示 といった基本構造は正しく動作しています。 そのため、コードをすべて作り直すのではなく、 平滑化の追加 ノイズの抑制 正規化の改善 といった 局所的な修正のみを依頼する方針にします。 AIに修正を依頼する際は、このように 変更範囲を明確に指定することが重要です。 もし「改善してください」とだけ書くと、AIが アーキテクチャを変更する ファイル構成を変える 不要な抽象化を追加する といった大きな変更を行ってしまうことがあります。 今回はそうした事態を避けるため、既存構造を維持したまま表示を安定化させるように依頼します。 修正を依頼したプロンプト 今回Codexには次のような指示を渡しました。 The app builds and runs correctly, and microphone input is being captured. However, the spectrum bars are too unstable and flicker heavily. Please improve the current implementation by: • increasing smoothing for the spectrum values • adding a simple noise floor threshold to suppress very small fluctuations • improving normalization so the bars are visually more stable • aggregating FFT bins into display bars more smoothly if needed Constraints: • keep the current architecture • do not rewrite the whole project • preserve SwiftUI + AVAudioEngine + Accelerate • keep the implementation simple and educational • show focused code changes only • briefly explain why each change improves visual stability 上記のプロンプトでは、次の点を意識しています。 現在の状態を明確に伝える ビルドは成功している 音声入力は取得できている という状態を明示しています。 これにより、AIが不要な部分を作り直す可能性を減らします。 修正内容を具体的に指定する 今回の問題に対して必要な処理は、主に次の3つです。 平滑化(Smoothing) ノイズ除去(Noise Floor) 正規化(Normalization) この3点を明確に指定することで、AIが意図した方向に修正を行いやすくなります。 変更範囲を制限する 特に重要なのが次の部分です。 keep the current architecture do not rewrite the whole project AIに対してこの制約を入れておかないと、既存コードを大きく変更してしまうことがあります。
今回は 安定化だけを行うことが目的なので、アーキテクチャは維持するように指示しています。 修正後の動作確認 Codexによる修正を適用したあと、再度ビルドと動作確認を行いました。 結果として、ビルドは引き続き成功し、アプリの基本機能も問題なく動作しています。 ビルド結果:BUILD SUCCEEDED アプリ起動:正常 マイク入力:正常に取得 スペクトラム表示:安定化 修正前の状態では、スペクトラムバーがフレームごとに激しく揺れてしまい、
無音に近い状態でも細かい変動が発生していました。 しかし今回の修正によって、 バーの動きが滑らかになった 無音時の微小な揺れがほぼ消えた 音量の変化に対する反応が自然になった といった改善が確認できました。 特に効果が大きかったのは、次の処理です。 上昇・下降で異なる係数を使う時間的平滑化 ノイズフロアによる微小入力の抑制 動的正規化によるスケールの安定化 FFTビンの平均化と空間的平滑化 これらの処理により、
スペクトラム表示はリアルタイム音声ビジュアライザーとして十分に安定した挙動になりました。 初回生成のコードでも基本機能は成立していましたが、
今回の調整によって 実際に見やすいスペクトラム表示 に近づいた形になります。 しかし、自分の環境ではまだ雑音が入っているのか左側のバーが常に動いているため
更に修正を加えます。 追加調整:環境ノイズへの対策 前節の修正により、スペクトラム表示はかなり安定しました。
バーの動きも滑らかになり、無音時の微小な揺れも大幅に減っています。 しかし実際にアプリを動かしてみると、
完全な無音状態でもわずかにバーが反応することが確認できました。 これはバグというより、音声入力を扱うアプリではよくある現象です。 マイク入力には、 マイク自体のノイズ 空調やPCファンなどの環境音 A/D変換時の量子化ノイズ などが含まれるため、完全なゼロ値になることはほとんどありません。 そのため、FFT解析を行うと
人間には聞こえないレベルのノイズでもスペクトラムに現れることがあります。 今回の実装でも、ノイズフロアを導入していましたが、
実機環境ではまだわずかな変動が残っていました。 そこで追加の対策として、 ノイズフロアを少し引き上げる 低レベル入力をより強く抑制する という調整を行いました。 ノイズフロアの調整 修正前は次の値を使用していました。 noiseFloorDb = -62 この値をやや高めに設定することで、環境ノイズの影響をさらに抑えることができます。 例えば次のように調整します。 noiseFloorDb = -25 この変更により、 小さすぎる入力はスペクトラムに表示されない 無音時のバーの揺れがほぼ消える という効果が得られました。 実装で重要なポイント 音声ビジュアライザーでは、解析結果の正確さよりも視覚的な安定性が重要になる場合があります。 そのため多くの実装では、 ノイズフロア 平滑化 正規化 バンド平均化 といった処理を組み合わせて、見やすいスペクトラム表示を作っています。 追加調整:ノイズフロア変更による副作用 前節では環境ノイズの影響を減らすためにノイズフロアを引き上げました。
実機環境でテストした結果、ノイズフロアを -12 dB に設定すると、
ほとんどの環境ノイズを抑えることができました。 この変更により次の改善が確認できました。 無音状態でのバーの揺れがほぼ消える PCファンや空調などの微小ノイズを拾わない スペクトラム表示がより安定する 音声解析では、FFTによって音声を周波数成分に分解しますが、
マイク入力には必ず微小な環境ノイズが含まれます。
これらのノイズは周波数領域でも小さな揺らぎとして現れるため、
可視化では微小なバーの動きとして表示されることがあります。 このような問題を防ぐため、一定以下のレベルを無視する ノイズフロア処理 を導入しました。 発生した問題 しかし、この調整には副作用がありました。 ノイズフロアを高く設定したことで、スペクトラム表示に次の変化が現れました。 バーが全体的に高い位置に集中する 周波数ごとの差が小さくなる スペクトラムの形状が分かりにくくなる つまり、ノイズは抑えられたものの、
スペクトラムのダイナミックレンジが圧縮されてしまった状態です。 今回の実装では、FFT結果をデシベル値へ変換し、
その値を一定の範囲で正規化してバーの高さへ変換しています。 ノイズフロアを大きく変更すると、 最小値が上がる 正規化範囲が狭くなる 全体の値が上側へ寄る という現象が発生します。 その結果、スペクトラムバーが 高い位置でまとまる表示 になってしまいました。 対応方針 この問題を解決するため、次の調整を行うことにしました。 正規化レンジの再調整 ノイズフロア適用後のスケーリング改善 スペクトラム分布を自然にする補正 つまり ノイズは抑える スペクトラムの形状は維持する というバランスを取る形です。 Codexへの修正依頼 この調整はアルゴリズムの細かい部分に関係するため、
既存のコード構造を維持したまま 局所的な修正 をCodexへ依頼しました。 実際に渡したプロンプトは次の通りです。 The app runs correctly and the noise floor adjustment successfully suppresses ambient noise. However, after setting the noise floor to -12 dB, the spectrum bars tend to cluster at high positions and the spectral shape becomes less distinguishable. Please improve the normalization and scaling so that: - the bars use a wider dynamic range - spectral differences between frequency bands remain visible - the spectrum does not collapse into a narrow high-level band Constraints: - keep the current architecture - preserve SwiftUI + AVAudioEngine + Accelerate - do not rewrite the project - adjust normalization or scaling logic only - keep the implementation simple and suitable for educational purposes Briefly explain the reasoning behind the changes. AIとの反復改善 今回のケースは、AIを使った開発の特徴がよく表れている例です。 初回生成では 音声入力 FFT解析 スペクトラム表示 といった基本機能は問題なく実装されていました。 しかし実際にアプリを動かしてみると、 ノイズ抑制 正規化のバランス 視覚的な分かりやすさ といった 体験品質に関わる部分は、細かな調整が必要でした。 そのため今回の開発では AIが土台を生成する
↓
実機で挙動を確認する
↓
問題を整理する
↓
AIに局所的修正を依頼する という反復プロセスで改善を進めています。 まとめ 今回は SwiftUI・AVAudioEngine・Accelerate を使った
リアルタイム音声ビジュアライザーのサンプルアプリを、Codexと協力して作成しました。 開発の流れを振り返ると、次のようなステップで進んでいます。 SwiftUIプロジェクトを作成 マイク権限を設定 Codexに実装を依頼 初回生成コードをビルドして動作確認 スペクトラム表示の不安定さを修正 ノイズフロアを導入して環境ノイズを抑制 正規化やスケーリングを調整して表示品質を改善 初回生成の段階でも、Codexは 音声入力の取得 FFTによる周波数解析 SwiftUIによるスペクトラム表示 といった基本機能を正しく実装していました。 しかし実際にアプリを動かしてみると、 バーの揺れ 環境ノイズの影響 正規化による表示バランス など、動作品質に関する細かな問題が見えてきます。 今回の開発では、 AIが土台を生成する
↓
人間が実機で挙動を確認する
↓
問題点を整理する
↓
AIに局所的な修正を依頼する という 反復的な開発プロセスで改善を進めました。 これは実際の開発でもよく見られるスタイルで、
AIを「自動コード生成ツール」として使うというよりも、 設計や問題分析を人間が行い、実装の補助をAIに任せる という役割分担で作業を進めるものです。 昨今、すべてをAIAgentに任せて最終製品を作れるという記事は増えていますが、
結局の所、最終的な製品の調整は人間が担う事になります。 その時、どういった制限をプロンプトにいれながら進めれば破綻しないのか、
といった部分の理解の一助になれば幸いです。 参考記事 生成AIと作るリアルタイム音声ビジュアライザー (準備編)
生成AIと作るリアルタイム音声ビジュアライザー (実装編) -
生成AIと作るリアルタイム音声ビジュアライザー (実装編)
はじめに 前回(生成AIと作るリアルタイム音声ビジュアライザー (準備編))では、
実装を行うための環境構築を行いました。
この記事では、実際にCodexに渡したプロンプトの説明を行いつつ、
実際に生成されたコードを動かし、修正を行っていきます。 Codexに渡すプロンプト ここからは、実際に Codexへどのような指示を出したか を整理していきます。 今回のようにAIを使用して作成するアプリでは、最初のプロンプト設計がかなり重要です。
理由は、AIに対して曖昧な依頼をすると、 不要に複雑な構成になる UIKitベースの実装が混ざる FFTまわりが雑になる 動いても記事にしづらいコードになる といった問題が起きやすいためです。
そのため今回は、最初から役割と制約を明示したプロンプト を使用します。
おまけ:
実際の開発や継続的なメンテナンスにおいてはAGENTS.mdに主要なルールを記載し、
プロジェクト特有のルールを mdファイルとして配下のディレクトリに設置するなどします。
興味があればハーネスエンジニアリング(Harness Engineering)で調べてみてください。
(この言葉は元々違う分野で一般的に使用される言葉でしたが、
2026年段階で主にAIAgent系の記事で頻出するようになってきています。) 今回のプロンプトで意識したこと 今回Codexに依頼するにあたって、特に意識したポイントは次の通りです。 「まず動くもの」を優先する 最初から理想的な設計を求めるのではなく、
まずは 小さくても動くサンプル を作ることを優先します。 音声処理まわりは、設計をきれいにしようとすると一気に複雑になりやすいため、
初回はあくまで マイク入力が取れる スペクトラムが表示される SwiftUIで動く という最低限の成立を目標にします。 過剰な抽象化を避ける AIは放っておくと、 Protocol Manager Coordinator Utility層 のようなものを増やしがちです。 もちろん実務では有効な場合もありますが、今回の記事の目的は 学習用サンプルを作ること です。 そのため、 ファイル数は少なめ 責務は分かれている ただし過剰設計はしない というバランスを狙います。 SwiftUI / AVAudioEngine / Accelerate を明示する 今回の技術スタックは最初から決めています。 UIは SwiftUI 音声入力は AVAudioEngine 解析は Accelerate / vDSP このあたりを曖昧にすると、AIが別の実装方式を混ぜてくることがあります。 特に音声系では、 AVAudioRecorder AudioKit風の構成 UIKitベースのView 必要以上に複雑なDSP処理 などに寄ってしまう可能性があるため、使う技術は明確に指定しておきます。 記事向けの読みやすさを重視する 今回作りたいのは「製品コード」ではなく、
技術記事の中で解説しやすいサンプルコード です。 そのため、プロンプトの中でも 小さい構成にする 読みやすい命名にする コメントを適度に入れる デバッグしやすい構造にする といった点を明示しています。 実際に渡したプロンプト 以下のような意図を踏まえたうえで、Codexには次のような依頼を行いました。
(プロンプトは英語に翻訳したうえで投入しています) Create a small but working iOS sample app using SwiftUI that visualizes live microphone input in real time. Goal: - Build a technical sample app for a blog article - Keep the code easy to read and easy to debug - Prioritize a simple working implementation over advanced architecture Tech requirements: - SwiftUI for UI - AVAudioEngine for microphone capture - Accelerate / vDSP for FFT or frequency-domain magnitude analysis - iOS 17+ - Swift - No third-party libraries - No UIKit App behavior: - Request microphone permission - Capture live microphone input - Analyze the input continuously - Display a real-time spectrum visualizer using vertical bars - Use a reasonable number of bars such as 32 or 48 - Normalize the values so the bars look visually stable - Add light smoothing to reduce flicker Architecture: - AudioAnalyzer: ObservableObject responsible for AVAudioEngine setup, audio tap, FFT, normalization, smoothing, and publishing spectrum data - SpectrumBarView: SwiftUI view that renders the bar graph - ContentView: owns AudioAnalyzer with @StateObject and starts/stops audio analysis - App entry point Implementation constraints: - Keep the project intentionally small - Avoid unnecessary protocols, managers, coordinators, or excessive abstraction - Prefer beginner-friendly naming - Add concise comments around important learning points - Publish UI updates on the main thread - Avoid expensive work on the main thread when possible - Keep the code structured for a technical article Deliverables: 1. Full source code for all files 2. File structure 3. Notes for Info.plist microphone permission key 4. Short explanation of key implementation decisions 5. Mention likely pitfalls Important: - The output must be real compilable Swift code - Do not use placeholder pseudocode - Do not over-engineer - Make the first version easy to iterate on after bugs are found このプロンプトで狙ったこと このプロンプトでは、単に「音声ビジュアライザーを作って」と依頼するのではなく、
次の点を最初から固定しています。 SwiftUIで作る AVAudioEngineを使う FFTはAccelerate系で行う 表示はバーグラフ形式にする 32〜48本程度の見やすい本数にする 正規化と平滑化を入れる 小さく読みやすい構成にする 特に重要なのは、最初から「小さく・動く・読みやすい」ことを優先している点です。 音声処理のような題材では、AIに自由度を与えすぎると
「一見すごそうに見えるが、実際には直しにくいコード」が出てくることがあります。 今回は技術記事化も前提にしているため、
完成度よりもまず 反復しやすさ を重視しました。 ここでのゴール この段階でのゴールは、完成品を一発で得ることではありません。 まず動く最初の版を出してもらう Xcodeでビルドする エラーや不具合を確認する 必要な修正を再度依頼する という流れを前提にしています。 今回のCodexの使い方は 一発生成型ではなく、反復改善型の使い方 です。 この方が実際の開発にも近く、記事としても再現しやすい構成になります。 実際になげてみる しばらく待ってみると以下のように ファイルが生成されます 更に実機でデバッグ実行してみる 特にエラーが起きることもなく以下のような画面が表示されます。 出力されたアプリの状況 何も手を加える事なく起動し、音声も取れていますが、常に左側のバーが高い状態のため、
ノイズをそのまま出力してる状態の様に見えます。 今の状態をまとめると ビルドOK 起動OK マイク入力OK ただし表示が期待どおりでない。 なので、ビルドエラーなどの対処はありませんでしたが、修正したい部分があったので、
「表示安定化」 を考えた上で修正を行っていきます。 考えられる原因 この現象の原因は、FFT(高速フーリエ変換:Fast Fourier Transform)結果の扱い方にありそうです。 FFTの出力は、そのまま使用すると非常に変動が大きく、
フレームごとに値が大きく上下します。 特にマイク入力では、 マイク自体のノイズ 空調音 微小な環境音 などもすべて数値として現れます。 そのため、FFTの値をそのまま表示するとスペクトラムが過敏に反応してしまうことがあります。 音声ビジュアライザーでは、解析の正確さだけでなく視覚的な安定性も重要になります。 そのため多くの実装では次のような処理が行われます。 値の平滑化(Smoothing) 微小ノイズの除去(Noise Floor) 表示用の正規化(Normalization) ここまでのまとめ 実際にAIに生成してもらったコードで、基本的な機能は実装できていましたが、
表示に不満が発生し、何が原因なのかを考えました。 この原因となる部分に関しては、
世間にあるアプリを見ても各社さまざまな調整がされており差が発生している部分であり、
こういった部分が人間が考慮したうえで調整を行う必要があります。 次回はこの問題をCodexに依頼することで修正していきます。 参考記事 生成AIと作るリアルタイム音声ビジュアライザー (準備編) -
生成AIと作るリアルタイム音声ビジュアライザー (準備編)
はじめに CoreAudioまわりのサンプルは、理解しようとすると少しハードルが高い領域です。
今回は題材として リアルタイム音声ビジュアライザー を選び、AIコーディングツール Codex を使って SwiftUI ベースのサンプルアプリを作ってみます。 この記事では、 SwiftUI AVAudioEngine Accelerate (FFT) Codex を使いながら、AIとペアプロする形でサンプルアプリを完成させていく過程をまとめます。 なお、最初から完成コードを出すのではなく、 AIに実装を依頼する ビルドエラーや挙動の問題を修正する 最終的に動くサンプルへ育てていく という流れで進めます。 今回はまず Codexに指示を出す前の準備までを扱います。 作るアプリ 今回作るアプリはシンプルです。 マイク入力の音をリアルタイムで解析し、周波数スペクトラムを表示するアプリです。 次のような構造で動作します。 Microphone
↓
AVAudioEngine
↓
Audio Buffer
↓
FFT(Accelerate / vDSP)
↓
Spectrum Data
↓
SwiftUI View 表示は以下のようなイメージになります。 █ ▇ ▅ ▃ ▄ ▆ █ ▇ ▅ ▃ ▄ ▆ █ なぜSwiftUIを使うのか 今回は UIKit ではなく SwiftUI を使用します。 理由は主に2つです。 UIコードを短くできる 音声ビジュアライザーの主役は 音声処理(Audio Processing) です。
UIKitを使うと、どうしても次のような実装が必要になります。 AutoLayout CALayer drawRect View更新管理 結果として UIコードの比率が大きくなり、本筋の音声処理から離れてしまうことを避けるためです。 SwiftUIなら、スペクトラム表示も非常にシンプルに書けます。 例えば、HStack の中で Rectangle を並べるだけでバー表示のベースを作ることができます。 HStack で横方向に配置 ForEach でスペクトラムデータをループ Rectangle でバーを描画 このように 少ないコード量でリアルタイムグラフの基礎が作れるため、今回はSwiftUIを使用します。 AI生成コードとの相性が良い 今回の記事では AIを使用して音声処理アプリを実装する をテーマにしています。 SwiftUIは宣言的UI(Declarative UI)のため、 UIKit AutoLayout CALayer操作 などと比較して AIが生成するコードの成功率が高い傾向があります。 特に次の点で相性が良いと感じています。 View構造がシンプル 状態管理が明確 UI更新がデータバインディング中心 UIKitの場合、AIが生成したコードでは次のような問題が起きることがあります。 AutoLayout制約が崩れる View更新のタイミングが不適切 CALayer描画処理が複雑になる 一方SwiftUIでは、UIの更新は基本的に 状態の変化によって自動的に反映されます。 そのため、 データ(スペクトラム配列)を更新する SwiftUIが自動で画面を更新する というシンプルな構造になります。 今回のサンプルでも、 マイク入力を取得する FFTでスペクトラムデータを作る 配列を更新する SwiftUIがグラフを再描画する という 非常に素直なデータフローを作ることができます。 結果として、AIが生成したコードをそのまま動かせる確率が高くなるため、
手動による修正や追加実装のコストを下げる事にも有用です。 プロジェクト作成 まずは XcodeでSwiftUIプロジェクトを作成します。 メニューから次を選択します。 File → New Project → App プロジェクト設定は以下のようにします。 Product Name : LiveAudioVisualizer Interface : SwiftUI Language : Swift Deployment : iOS 17+ プロジェクトを作成すると、最小構成のSwiftUIアプリが生成されます。 LiveAudioVisualizer ├ LiveAudioVisualizerApp.swift └ ContentView.swift この段階では、まだ音声処理のコードは存在していません。 マイク権限の設定 iOSでマイクを使用するアプリでは、
Info.plistに使用理由を記述する必要があります。 この設定が無い場合、アプリはマイクにアクセスできません。 SwiftUIテンプレートの場合、Info.plist がファイルとして見えないことがあります。
その場合は Target設定のInfoタブから追加します。 TARGETS → LiveAudioVisualizer → Info 追加するキーはこちらです。 Privacy - Microphone Usage Description 値は例えば次のようにします。 Microphone access is required for audio visualization. この文章は、ユーザーが初めてマイクアクセスを許可する際に表示されるメッセージになります。 まとめ 今回は、リアルタイム音声ビジュアライザーを作るための 準備段階として、 SwiftUIを採用した理由 Xcodeでのプロジェクト作成 マイク権限の設定 までを行いました。 この段階ではまだアプリとしての機能はほとんどありませんが、
音声入力を扱うiOSアプリの土台は整った状態になります。 次のステップでは、いよいよ AI(今回はCodex)に実装を依頼します。 具体的には、 マイク入力の取得(AVAudioEngine) 音声データの解析(Accelerate / FFT) スペクトラムデータの生成 SwiftUIでのリアルタイム表示 といった部分を、AIと協業する形で実装していきます。 また、AIが生成したコードは必ずしもそのまま動くとは限らないため、 ビルドエラーの修正 動作の調整 表示の安定化 といった作業も含めて、実際の開発フローに近い形で進めていきます。 次回は、Codexに渡した 実際のプロンプト と、
そこから生成されたコードを見ながら、実装を進めていきます。 -
Tomcat が2週間でフリーズする問題を解決した話
はじめに Spring Boot + Spring Data JPA で構築したバッチ処理が、テストフェーズ(ロングランテスト)で約2週間稼働させると Tomcat がフリーズするという問題に遭遇しました。 調査した結果、IN 句を使ったクエリが原因でメモリを圧迫していることがわかり、Hibernate の設定を1つ追加することで改善しました。同じような問題で困っている方の参考になればと思います。 環境 項目バージョン / 構成Java8Spring Boot2.7.4Spring Data JPA2.7.3サーバーAWS EC2 (Amazon Linux 2)AP サーバーTomcatDBAWS Aurora MySQL 発生した問題 症状 バッチ処理を開発環境で稼働させていると、約2週間で Tomcat がフリーズする フリーズ後はバッチ処理が動かなくなる Tomcat を再起動すると復旧するが、また2週間程度で同じ現象が発生 症状をもとにした仮説 徐々にメモリリークしていき、最終的にヒープメモリが枯渇してフリーズしたものと考えられました。 試したこと メモリリークが原因という仮説を立てたものの、具体的なメモリリーク発生個所の特定がなかなかできませんでした。 処理の見直し:不要なオブジェクトを保持していないか、ループ内でのオブジェクト生成を減らせないかなど確認 EC2 のスペック変更:メモリを増やして様子を見たが、フリーズするまでの時間が延びただけで根本解決にはならず GC のチューニング:ヒープサイズやGCの設定を調整したが改善せず いろいろ試しても解決しなかったため、ウェブ検索で情報を探しました。「Spring Boot メモリリーク」「Tomcat フリーズ 原因」など検索キーワードを変えながら調べ続けて、ようやく IN 句のパラメータ数が原因になりうるという情報にたどり着きました。 原因 調査の結果、IN 句を含むクエリの種類が多すぎることが原因でした。 何が起きていたか バッチ処理で Spring Data JPA の findByIdIn() のようなメソッドを使っていました。 // 例:IN句のサイズが毎回異なる List<Entity> result1 = repository.findByIdIn(List.of(1, 2, 3)); // 3個 List<Entity> result2 = repository.findByIdIn(List.of(1, 2, 3, 4)); // 4個 List<Entity> result3 = repository.findByIdIn(List.of(1, 2, 3, 4, 5)); // 5個 このとき、IN 句に渡すリストのサイズが毎回異なると、サイズごとに別の SQL 文として扱われます。 -- 3個の場合 SELECT * FROM entity WHERE id IN (?, ?, ?) -- 4個の場合 SELECT * FROM entity WHERE id IN (?, ?, ?, ?) -- 5個の場合 SELECT * FROM entity WHERE id IN (?, ?, ?, ?, ?) Hibernate はクエリごとにパース結果をメモリ上に保持します。IN 句のサイズが 1〜1000 まで変動するような処理だと、1000種類のクエリがメモリに溜まっていくことになります。 今回のバッチ処理では IN 句に渡す件数がバラバラだったため、これが積み重なってメモリを圧迫していました。 解決策 in_clause_parameter_padding を有効にする Hibernate には、IN 句のパラメータ数を揃えてくれるオプションがあります。 application.properties に追加 spring.jpa.properties.hibernate.query.in_clause_parameter_padding=true このオプションの動き IN 句のパラメータ数を、次の切りのいい数に揃えてくれます。 実際のパラメータ数揃えた後1〜223〜445〜889〜161617〜3232 たとえば、3件渡すと4件用の SQL になり、5件渡すと8件用の SQL になります。足りない分は同じ値で埋めてくれます。 このようにすることで、パラメータ数がバラバラでも生成される SQL の種類が限定され、メモリの消費を抑えられます。 結果 この設定を追加した後、2週間以上稼働させても Tomcat がフリーズしなくなりました。 コードの修正は不要で、設定ファイルに1行追加するだけで解決できたのは助かりました。 注意点 このオプションは Hibernate 5.2.18 以降で使えます 足りないパラメータは同じ値で埋めてくれるので、SQL のログを見ると少し違和感があるかもしれません 適用前後でクエリの実行結果が変わることはありません まとめ Spring Data JPA で IN 句を使う処理が多いと、クエリの種類が増えてメモリを圧迫することがあります in_clause_parameter_padding=true を設定すると、クエリの種類を減らせます 設定1行で改善できるので、似たような症状が出たら試してみてください 参考 https://www.baeldung.com/java-hibernate-in-clause-padding https://vladmihalcea.com/improve-statement-caching-efficiency-in-clause-parameter-padding/