シリーズ:iOS版『見開きPDFビューア』開発記録
この記事は、ChatGPTと壁打ちしながら作成しました。また、開発時の作業ログと、Gitの変更履歴・ソースコードを調査した結果を含みます。
MihirakiPDFViewerは、GemmaとClineで基本機能を作った段階から、画質や見開き表示、検索の調整を重ねてきました。第3回ではテストPDFによる動作確認、第4回では読書位置や設定の復元、外部PDFの取り込みを紹介しました。
今回は、動作を確認した端末の情報を残す診断機能と、不具合が起きるまでの経過を調べるログ機能を取り上げます。開発中にCodexへ脆弱性の確認を依頼した経緯も紹介します。2026年8月から10月の作業ログと、10月20日時点で調査したtestingブランチの91f0853をもとに振り返ります。
動作を確認した端末の情報も残す
実機で試した結果を振り返るには、どの端末で、どのOSとアプリのバージョンを使ったのかも必要になります。10月6日0時54分の作業ログに、実機でテストを実行するボタンを追加し、結果に日時、端末名、OSとアプリのバージョンを含め、さらに共有機能を追加するように依頼したことが残っています。
その後、10月15日にはAndroid版を参考に、RAMやストレージなどを結果に追加するように依頼しています。同時に、端末名を機種名・機種識別子へ置き換え、個人を特定する可能性があるシリアル番号は含めないよう指定しました。
図1 iPad(A16)/iPadOS 26.5のSimulatorで2026年10月22日に撮影した診断画面。機種名・機種識別子、OSとアプリのバージョンを確認できます。
共有する端末情報に個人名を含めない
個人情報を含めないようにするという依頼に対し、Codexは「端末名は利用者の個人名を含むことがある」として、診断結果の共有時には省略することなどを提案しました。この提案を受けて、診断に必要な機種名・機種識別子へ置き換えることにしました。10月16日の変更にも、その切り替えが残っています。
端末名を取得するAPIの仕様は、本稿を執筆中にAppleの公式資料で確認しました。Appleの開発者資料によると、UIDevice.nameはiOS 15以前ではユーザー設定の端末名を返し、iOS 16以降では通常「iPhone」などの一般名を返します。iOS 16以降でも、専用のエンタイトルメントが付与されていればユーザー設定の名前を取得できます。AppleのUIDevice.nameの説明
開発当時は、Appleの公式資料を詳しく読んでいたわけではありません。それでも、Codexの提案を受けて、診断に必要な機種名と機種識別子を表示するコードを実装できました。必要な仕様をすべて自分で調べる前に、提案を手がかりに実装を進められたことは、生成AIを利用したメリットの一つだったと感じています。
図2 端末名と機種名の違い、およびOS・専用権限によってUIDevice.nameが返す値の違い。
端末上で基本的な処理を確かめる
第3回では、ページの並びや検索件数が分かっているPDFを用意し、期待した結果になるかを確かめました。端末診断は、それとは違う役割を持ちます。アプリが動いている端末で、基本的な処理ができるかを確認する仕組みです。
DeviceTestRunnerでは、アプリのバージョン情報、キャッシュ保存先への書き込みと読み取り、空きストレージ、一時的に生成したPDFの読み込みを確認しています。結果には端末のモデル、OSとアプリのバージョン、メモリ情報などをまとめ、共有用の文章にする処理もあります。
PDFの項目では、1ページの小さなPDFを生成し、PDFKitで読み込めるかを確認します。これが成功しても、1,100ページのPDFでの操作や、すべてのPDFの表示を確認できたことにはなりません。固定したテストデータによる検証と、端末上での簡易診断を組み合わせて使う構成です。
図3 DeviceTestRunnerの4つの確認項目と、実行環境・結果を共有用の文章にまとめる流れ。
不具合の原因を探るためにログを残す
端末の基本的な状態を確認するだけでは、問題が起きるまでに何があったかは分かりません。10月6日1時13分の作業ログに、操作手順、クラッシュ、スローダウン、動作や表示の不良の原因を特定することを目的に、ロギング機能の追加を依頼した記録が残っています。
この依頼には、個人情報やファイルの内容を記録しないこと、ログの表示と共有ができること、外部へ自動送信しないこと、24時間を経過したログを削除することも含まれています。原因を調べるための記録と、記録しない情報の範囲を、同時に指定していました。24時間を経過したログを削除する指示をしたのは、ログが肥大化してストレージを圧迫する問題を起こさないようにするためです。
図4 操作に対応するイベントを記録し、問題が起きるまでの経過を調べる考え方。
ログに自由な文章を渡さない
本稿で確認した AppLogStore では、記録するイベントを AppLogEvent という列挙型で定義しています。
たとえば、文書読み込み開始、検索完了、ページ描画の遅延などです。呼び出し側が任意の文字列を渡す構造ではなく、定義済みイベントに処理時間や数値を添えて記録します。
func record(
_ event: AppLogEvent,
at timestamp: Date = .now,
durationMilliseconds: Int? = nil,
numericValue: Int? = nil,
numericLabel: AppLogNumericLabel? = nil
)
この入口にはファイル名や検索語を渡す引数がありません。何を記録しないかを、説明文だけでなくデータの形にも反映した実装になっています。
保存処理は actor にまとめられ、アプリ内のログは24時間と最大2,000件を基準に絞り込まれます。厳密には、24時間が過ぎた瞬間に常駐処理で消すのではなく、記録・取得時に保持対象を選び直して保存する実装です。また、定義済みのイベント文言はOSのLoggerにも出力するため、この保持期間をOS側のログ全体の保証として扱うことはできません。
Codexに脆弱性の確認も依頼した
端末上で動作を確かめる診断機能とは別に、開発中のコードについても確認を依頼していました。8月21日6時58分(日本時間)の作業ログには、Codexに「ソフトウェアの脆弱性を診断したい。」と依頼した記録が残っています。
このとき、自分が重視していたのは、アプリを経由して本来アクセスできない領域へ到達される危険を減らすことでした。このアプリは個人情報を収集することを目的としていません。そこで、権限昇格などによってOSやストレージ、他のアプリへ不正にアクセスできる経路を減らすことが重要だろうと考えていました。
検索欄のように利用者が自由に文字列を入力できる箇所については、オーバーフローなどの脆弱性も意識していました。ただし、当時の自分の認識では、それらを対象とした本格的な検証や追加対策にまでは着手していませんでした。今回確認した作業ログでも、不正な入力や極端に長い検索語などを使い、入力処理の安全性を確認・修正するよう依頼した記録は見つかりませんでした。
Codexは、PDFなどの外部入力やファイルアクセス、外部通信、任意コード実行に関係するAPI、StoreKit、Info.plistの設定、外部ライブラリへの依存を確認しました。当時の回答では、ビルドの成功と、「高・中リスクのアプリ実装上の脆弱性は見つかりませんでした」という結果を報告しています。
一方で、PDFの読み込みに使うPDFKitなどの処理はOS側の実装に依存することも説明していました。そのうえで、極端に大きなPDFへの対策や、将来クラッシュログを確認できるようにすることを提案しています。
ここで行ったのは、当時のコードを読んで問題がないかを確認するレビューです。専用ツールによる網羅的な脆弱性検査を実施した記録ではなく、現在のコード全体の安全性を保証するものでもありません。それでも、自分から確認したい観点を伝え、コードを見直す機会を作れたことには意味があったと感じています。
図5 Codexに依頼した脆弱性確認のねらいと確認範囲。
状態と経過の両方を残す
端末診断で実行環境と基本的な動作を確認し、ログで問題が起きるまでの経過を調べる。何を確認するかに加え、何を記録しないかも指定しながら、調査に必要な仕組みを整えていきました。コードの脆弱性確認も含め、Codexに確認したい観点を伝えながら、アプリを見直してきました。
次の第6回では、Codexへコードの評価を依頼し、改善計画を実行した経緯を紹介します。
ソースコードと本稿の対象
ソースコードはGitHubのtyamada/MihirakiPDFViewerで公開しています。本稿は10月20日時点で調査したtestingブランチの91f0853を基準にしています。今回、アプリのビルドやテストの再実行は行っていません。
前の記事:第4回:読書位置・設定の復元と外部PDF
次の記事:第6回:コード評価からView分割・描画改善・CI導入へ(公開予定)




