EY-Office ブログ

今年も来ました 『 ”〜〜アプリ”はもう利用できません』今回はAIに手伝ってもらいました

はい、今年も開発ライセンスでデプロイしたアプリの有効期限が1年が切れました!
過去の『 ”〜〜アプリ”はもう利用できません』の記事: 2024年2023年2022年

今回もコンパイル&デプロイでOKとは行きませんでした。😅

app_can_not_use Gemini 2.5 Flash Imageが生成した画像をもとにしています

私の使っている自分専用のiOSアプリ

私の使っている自分専用のiOSアプリには以下の2つがあります。

  • TodoTasks : Todo管理アプリで、自分に使いやすいRedmineのフロントエンドiOSアプリ
  • WebArchives : ブラウザーで気になったページをPDFに変換しFirebaseに保存・閲覧するiOSアプリ

TodoTasksはRedmineのAPIとやり取りをしているだけの、シンプルなiOSアプリで特殊なライブラリー等は使っておらず、このアプリはコンパイル&デプロイで動作しました。😃

しかし、WebArchivesFirebase用のライブラリーを使ってい、これが今回もコンパイルエラーになってしまいまいました。

WebArchivesのエラー対策

毎年、Firebse関連のライブラリーで悩ませられます。まあ、しかし、広く使われているライブラリーは日々進歩するので仕方ないですよね。

1. FirebaseUI-iOSのブランチ変更

FirebaseUI-iOSをSwift Package Managerで使っているのですが、GitHubのmasterブランチがmainブランチに変わっていたのでXcodeの設定を変更しました。まあ、これは仕方ないですよね。

2. IPHONEOS_DEPLOYMENT_TARGETの変更

iOSアプリは古いバージョンのOSでもデプロイ出来るようにIPHONEOS_DEPLOYMENT_TARGETという設定があります。今までバージョン12.1になっていたのですが、これが原因でコンパイル出来なくなっていました。自分で使うアプリなのでデプロイできるバージョンは18(最新の1つ前)に変更しました

3. unused警告

1.と2.の変更でコンパイル出来、iOSシミュレータにデプロイして動作を確認できました。😃  しかしBackend.swiftファイルで
Result of call to 'addStateDidChangeListener' is unusedという警告が出ていました。
自分専用アプリなので動けばOKなのですが、次回のバージョンアップでエラーになるかもしれないので、対応する事ににしました。

このモジュールはFirebaseAPIの呼出し関連のメソッドを集めたものですが、2019年に作ったのでコードの内容をすっかり忘れていました。
そこで、ClaudeCode君に助けてもらう事にしました。

Claude Code V2 カワイイ

コードの調査

寸前に書いたように、私自身もコードの詳細を忘れてしまったのでAIにコードを読んでもらい、処理内容をレポートしてもらいました(たぶん次の警告の調査にも役立つと思われます)。
これで、コードを読んで思い出すのが楽になりますした。😃

> WebArchives/Backend.swiftの処理内容をレポートしてください  
 
⏺ WebArchives/Backend.swiftファイルの内容を読み取って、処理内容をレポートします。 
 
⏺ Read(WebArchives/Backend.swift) 
  ⎿  Read 225 lines 
 
⏺ Backend.swift 処理内容レポート 
 
  概要 
 
  WebArchivesアプリケーションのバックエンドロジックを担当するクラスで、Firebase(Firestore、Storage、Authentication)との連携を行いま 
  す。 
 
  --- 
  主要な構造体 
 
  ArticleQueryOption (16-24行目) 
  - 記事検索のオプション設定 
  - orderColumn: ソート対象カラム 
  - orderDescending: 降順/昇順フラグ 
  - category: カテゴリフィルタ 
 
  Article (26-51行目) 
  - 記事データモデル(Codable準拠) 
  - Firestore DocumentSnapshotから初期化 
  - フィールド: documentID, title, category, pdf, url, createdAt, thumb 
 
  ArticlesJson (53-62行目) 
  - 記事配列のJSON変換用ラッパー 
 
  --- 
  Backendクラスの主要機能 
 
  1. 認証関連 
 
  - authorize(_:complete:) (75-83): Google認証の初期化とログイン状態確認 
  - logout() (85-87): ログアウト処理 
  - checkLoggedIn(viewController:complete:) (204-215): ログイン状態監視、未ログイン時は認証画面表示 
 
  2. 記事管理 
 
  - addArticle(data:complete:) (89-99): Firestoreに新規記事を追加 
  - queryArticles(option:useCache:complete:) (101-135): 
    - 記事一覧の取得(カテゴリフィルタ、ソート対応) 
    - キャッシュ機能搭載(~/Library/Caches/articles) 
    - 取得結果をJSONでキャッシュ保存 
  - deleteArticleAndPdf(article:) (178-191): 記事とPDFの削除 
  - deleteArticle(articleId:) (193-202): Firestoreから記事ドキュメント削除 
 
  3. カテゴリ管理 
 
  - allCategories(complete:) (137-150): カテゴリ一覧をorderフィールドで昇順取得 
 
  4. PDF管理(Firebase Storage) 
 
  - uploadPdf(path:complete:) (152-163): 
    - ローカルPDFをFirebase Storageにアップロード 
    - pdf/yyyyMMddHHmmss_XXXX.pdf形式で保存 
  - downloadPdf(pdfName:complete:) (165-176): 
    - Firebase StorageからPDFをダウンロード 
    - 一時ディレクトリに保存 
 
  5. ユーティリティ 
 
  - filenameByTime() (217-222): タイムスタンプベースのファイル名生成(乱数付加) 
 
  --- 
  技術的特徴 
 
  - キャッシング: 記事データをJSONでローカルキャッシュし、パフォーマンスを向上 
  - 非同期処理: 全てのFirebase操作で完了ハンドラーを使用 
  - 認証フロー: FirebaseUIを利用したGoogle OAuth認証 
  - エラーハンドリング: 各操作でエラーログを出力(ただし一部force unwrapあり) 
警告の調査・対応

試しに、警告を調べてもらいました。原因の説明はもっともらしいですね。そして修正方法までありました。しかも、3案も❗
対処方法の説明を読み、オプション2: 現在の状態を一度だけチェック を採用しました。

> 205行目でResult of call to 'addStateDidChangeListener' is unused 警告が出るのはなぜですか?  
 
⏺ 205行目のaddStateDidChangeListenerの警告について説明します。 
 
  警告の原因 
 
  Auth.auth().addStateDidChangeListenerはリスナーハンドルを返すメソッドです。このハンドルは後でリスナーを解除するために使用されます。 
 
  現在のコード: 
  Auth.auth().addStateDidChangeListener{auth, user in 
      // ... 
  } 
 
  返り値を使用していないため、Swiftコンパイラが「この返り値は重要かもしれないのに無視されている」と警告しています。 
 
  問題点 
 
  このコードでは、リスナーが登録されたままになり解除されないため、以下の問題が発生します: 
 
  1. メモリリーク: viewControllerやクロージャがメモリに残り続ける 
  2. 複数回実行: 認証状態が変わるたびにクロージャが繰り返し実行される 
  3. 意図しない動作: ログイン画面が複数回表示される可能性 
 
  修正方法 
 
  オプション1: リスナーハンドルを保持して適切に解除 
 
  private var authStateListener: AuthStateDidChangeListenerHandle? 
 
  private func checkLoggedIn(viewController: UIViewController, complete: @escaping () -> Void) { 
      authStateListener = Auth.auth().addStateDidChangeListener{auth, user in 
          if user != nil { 
              // リスナーを解除 
              if let listener = self.authStateListener { 
                  Auth.auth().removeStateDidChangeListener(listener) 
              } 
              complete() 
          } else { 
              print("-- Auth fail") 
              let authViewController = self.authUI.authViewController() 
              authViewController.navigationBar.topItem?.title  = "Login" 
              viewController.present(authViewController, animated: true, completion: nil) 
          } 
      } 
  } 
 
  オプション2: 現在の状態を一度だけチェック(推奨) 
 
  認証状態の監視が不要なら、currentUserで現在の状態を確認: 
  private func checkLoggedIn(viewController: UIViewController, complete: @escaping () -> Void) { 
      if Auth.auth().currentUser != nil { 
          complete() 
      } else { 
          print("-- Auth fail") 
          let authViewController = self.authUI.authViewController() 
          authViewController.navigationBar.topItem?.title  = "Login" 
          viewController.present(authViewController, animated: true, completion: nil) 
      } 
  } 
 
  オプション3: 警告を抑制(非推奨) 
 
  _ = Auth.auth().addStateDidChangeListener{auth, user in 
      // ... 
  } 
 
  現在の実装では、おそらくオプション2が最適です。authorizeメソッドで一度だけログイン状態を確認すれば良いため、継続的な監視は不要と思 
  われます。 
 
> /exit  
  ⎿  Bye! 
 
 
Total cost:            $0.0888 
Total duration (API):  39.8s 
Total duration (wall): 22m 23s 
Total code changes:    0 lines added, 0 lines removed 
Usage by model: 
    claude-3-5-haiku:  660 input, 79 output, 0 cache read, 0 cache write ($0.0008) 
       claude-sonnet:  13 input, 1.9k output, 36.6k cache read, 12.8k cache write ($0.0879) 

まとめ

最近はiOSアプリの開発は行っていないので、私のiOS/Swift開発力(開発に対する感覚)は、かなり落ちています。このような状況でのAIの助言は助かりますね。

もちろん、最終的な判断は人間が行う必要があるります。今回の警告の情報はネット上にたくさんあるので正しい修正案が出せたと思われます。ただし、今までAIに教えてもらったエラー等の調査結果が間違っていた事が何度もありました。😅

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ