iOSバージョン別の検出時間と登録上限はどう違う?
当社の実機計測では、iOS 17に約148個のシステムコード登録上限があることを確認しており、上限を超えるとポーリング時間が急増するどころか検出そのものが不可能になります。さらに、OSバージョンごとに動作原理が根本から異なるため、「登録数を減らせば速くなる」という対策が有効なケースとそうでないケースを見極めることが重要です。
iOS 16 / 17 / 18 / 26の実測データ比較
当社実測調査(2026年7月、各条件10〜20回の中央値)では、iOS 16・17・18・26の4バージョンで登録数を変えながら検出時間を計測しました。結果は以下のとおりです。
| OSバージョン(端末) | 登録10個 | 登録50個 | 登録145〜150個 | 登録200個以上 |
|---|---|---|---|---|
| iOS 16.7.5(iPhone 8) | 2.78秒 | 約15秒 | 約45秒 | 60.8秒(200個) |
| iOS 17.4.1(iPhone XR) | 0.031秒 | 約15秒 | 44.3秒(145個)/ 検出不可(150個) | 測定不可 |
| iOS 18.7.8(iPhone 13) | 0.054秒 | 0.054秒 | 0.054秒 | 0.054秒(500個まで確認) |
| iOS 26.5.2(iPhone 13) | 0.054秒 | 0.054秒 | 0.054秒 | 0.054秒(500個まで確認) |
出典: 当社実測(各機種・各OSバージョン、N=10〜500、各条件10〜20回の中央値、2026年7月)
iOS 16は登録数に比例して検出時間が線形に増加し、1コードあたり約0.3秒が加算されます(線形性±1%以内)。登録数が200個に達すると検出時間は60.8秒に達し、デフォルトのタイムアウト(60秒)に接触する水準です。
iOS 17は少数登録時だけ高速で、登録10個では0.031秒と非常に高速です。しかし50個を超えるとiOS 16と同じ線形動作に切り替わります。iOS 18以降は登録数に関わらず約0.054秒で一定であり、500個を登録しても変化はありませんでした。
計測上の注意点: FeliCaの検出はシステムコードを先頭から逐次ポーリングするため、目的のコードがリストのどこに位置するかで検出時間が変わります。この計測では最悪ケースを評価するために、目的コードをリストの末尾に配置して計測しています。
iOS 17の上限到達時に起きる挙動
iOS 17固有の問題は、「遅くなる」のではなく「検出が完全に不可能になる」点にあります。
当社実測では、iPhone XR(iOS 17.4.1)にシステムコードを150個登録した状態でFeliCaカードをスキャンしたところ、端末を再起動した後も同一の条件で検出不可が再現しました(出典: 当社実測、2026年7月)。145個登録時は44.3秒で検出を確認しているため、148個前後に上限が存在すると判断しています。
この挙動はタイムアウトエラー(NFCReaderError.readerSessionInvalidationErrorSessionTimeout)とは異なります。セッションを長時間維持しても結果は変わらず、個数そのものが原因です。iOS 16には同様の上限が確認されておらず、200個でも60.8秒で読み取れています。
実務上の対処方針を整理すると次のとおりです。
| 対象OSの下限 | 登録数の上限目安 | 根拠 |
|---|---|---|
| iOS 16 | 約10個 | 10個で2.78秒。これ以上増やすと許容レスポンスを超えやすい |
| iOS 17 | 148個未満 | 148個超で検出不可。かつ50個超から線形遅延が始まる |
| iOS 18以降のみ | 事実上制限なし | 500個でも0.054秒で一定(当社実測範囲内) |
iOS 17対応が必要なアプリでは、システムコードの絞り込みが単なる最適化ではなく動作保証の要件になります。具体的な絞り込み手順と記述方法は次のセクションで解説します。
まず確認すべき原因はどこにある?
多くの場合、Info.plistに登録するシステムコード数の過多、または書き方の誤りが読み取り遅延の直接原因です。端末の不具合やNFCアンテナの問題と混同されがちですが、設定ファイルの見直しだけで改善するケースが大半です。
Core NFCがFeliCaを検出する仕組み
Core NFCとは、iOSが提供するNFC読み取りフレームワークです。FeliCa読み取りでは、NFCTagReaderSessionが起動すると、iOSはInfo.plistに記載されたシステムコードのリストを参照し、上から順にポーリング(タグへの問い合わせ)を実行します。
この逐次的なポーリングシーケンスが、遅延の根本的なメカニズムです。登録コード数が多いほどシーケンスが長くなり、検出までの時間が伸びます。カードが最後のコードに対応している場合、リスト全体を走査してから初めて検出が成立します。
検出の流れを整理すると、次のとおりです。
NFCTagReaderSessionをpollingOption: [.iso18092]で開始する- iOSがInfo.plistの
com.apple.developer.nfc.readersession.felica.systemcodesを読み込む - 登録されたシステムコードを先頭から順にポーリングする
- カードが応答したコードで
NFCFeliCaTagインスタンスが生成される tagReaderSession(_:didDetect:)デリゲートが呼ばれ、アプリ側の処理が始まる
ステップ3の反復回数が多いほど、ステップ4の到達が遅くなります。コード数を絞ることが最初の改善策になる理由はここにあります。
遅延・タイムアウト・失敗の症状別チェックリスト
症状によって調査すべき箇所が異なります。以下を入口に、原因を絞り込んでください。
| 症状 | 主な原因の候補 | 最初に確認する箇所 |
|---|---|---|
| カードをかざしてから検出まで2〜5秒かかる | systemCodesの登録数が多い | Info.plistのコード数 |
| 一定時間後に毎回タイムアウトする | 登録上限超過、または対象コードが未登録 | iOSバージョンと登録数の組み合わせ |
| 特定の端末だけ失敗する | OSバージョン差による挙動の違い | iOSバージョン(詳しくは後述します) |
| カードの種類によって成否が分かれる | ワイルドカード指定の誤りまたは未使用 | 88B4の記述有無 |
| アプリ再起動後は読めるが、連続使用で失敗する | invalidationHandlerの未処理 |
セッション再生成の実装 |
| ビルド直後は動くが実機で失敗する | Entitlementsの設定漏れ | .entitlementsファイルとプロビジョニング |
症状が複数重なる場合、まずシステムコード数を削減してから再検証することを推奨します。単一の変数を変えて計測することが、原因特定を早めます。なお、iOSバージョンごとの検出時間の実測値と登録上限については後述のセクションで詳しく取り上げます。
Info.plistを正しく設定する手順
スキャン対象を最小限のシステムコードに絞り込んだうえで、Entitlementsファイルとの整合性を取ることが最短の改善策です。手順を3ステップに分けて説明します。
ステップ1: 必要なシステムコードを洗い出す
つまずきやすい点: 「念のため」で追加したコードが遅延の根本原因になっていることが多いです。
まず、アプリが実際に読み取るFeliCaカード・デバイスの種別を整理します。下表を使って、対象システムコードを棚卸ししてください。
| カード・サービス例 | 代表的なシステムコード | 本当に必要か |
|---|---|---|
| Suica / PASMO(交通系) | 0003 | 実装要件次第 |
| 楽天Edy | 8008 | 実装要件次第 |
| nanaco | 88B4(ワイルドカード) | 実装要件次第 |
| 独自FeliCaカード | 任意(要確認) | 仕様書で確認 |
洗い出しの手順は以下のとおりです。
- 仕様書またはカード発行元ドキュメントで、読み取るサービスのシステムコードを確認する。(所要時間の目安: 30分〜1時間)
- 一覧にしたコードのうち、現在リリース中の機能で実際に使っているものだけを残す。使っていないコードは削除する。
- 将来機能のためのコードは Info.plist には含めない。機能追加時にアプリ更新で追加するほうが安全です。
ワイルドカード指定(88B4)の扱いは、スキャン対象が広範になりiOS 16・17では遅延を招きやすいため、避けられる場合は個別コードに置き換えることを検討してください。ワイルドカードの詳細な注意点は別セクション「Info.plistのsystemCodesとは何か?」を参照してください。
ステップ2: Info.plistへの記述と型・形式の確認
つまずきやすい点: 値の型がStringでなくNumberになっていたり、16進数表記の大文字・小文字が混在したりするケースが現場で頻出します。
洗い出したシステムコードを Info.plist に記述します。キー名と型、書き方の要件を以下にまとめます。
| 項目 | 正しい設定 |
|---|---|
| キー名 | com.apple.developer.nfc.readersession.felica.systemcodes |
| 型 | Array(各要素はString) |
| 値の形式 | 4桁の16進数(大文字・小文字どちらも可だが、プロジェクト内で統一する) |
| 先頭の「0x」 | 不要。0003のように数字のみ記述する |
Xcodeのソースエディタ(plist XML)で記述する場合の例は次のとおりです。
<key>com.apple.developer.nfc.readersession.felica.systemcodes</key>
<array>
<string>0003</string>
<string>8008</string>
</array>
記述後に確認すべき点をリストアップします。
- Xcode左ペインでファイルを選択し、Property List形式で表示したときに各要素の型が「String」になっているか確認する。
- 登録するコードの総数を数える。iOS 16・17を対象に含む場合は、不要なコードが混入していないかもう一度確認する(iOSバージョン別の上限の詳細はセクション3を参照)。
- XMLを直接編集した場合は
plutil -lint Info.plistでシンタックスエラーがないか確認する。(所要時間の目安: 5分)
ステップ3: Entitlementsとの整合性チェック
つまずきやすい点: Info.plist に正しく書いても、Entitlementsファイルの com.apple.developer.nfc.readersession.formats に MIFARE や FeliCa が含まれていないと、実行時にセッションが即時終了します。
Info.plist の設定はEntitlementsファイルと対になって機能します。整合性を確認する手順は以下のとおりです。
<プロジェクト名>.entitlementsを開き、com.apple.developer.nfc.readersession.formatsキーが存在するか確認する。(所要時間の目安: 2分)- そのArrayに
MIFAREまたはFeliCaが含まれているか確認する。FeliCa専用アプリであればFeliCaのみで構いません。 - Apple Developer Portalでアプリの App ID に「NFC Tag Reading」ケーパビリティが有効になっているか確認する。Xcodeの「Signing & Capabilities」タブからも確認できます。
- 変更後はクリーンビルド(
Cmd + Shift + K)を実行してから実機でテストする。シミュレータはNFCを非サポートのため、必ず実機を使う。
下表で Info.plist とEntitlementsの対応関係を整理します。
| 設定ファイル | キー | 役割 |
|---|---|---|
| Info.plist | com.apple.developer.nfc.readersession.felica.systemcodes |
ポーリング対象のシステムコードを列挙する |
| Entitlements | com.apple.developer.nfc.readersession.formats |
NFCセッションで使用するタグ形式を宣言する |
| Apple Developer Portal | NFC Tag Reading ケーパビリティ | 実機実行の権限をプロビジョニングプロファイルに含める |
3つすべてが揃って初めてFeliCaの読み取りセッションが正常に開始します。どれか1つでも欠けると、NFCReaderError が即時返却されます。エラーコードの分類と invalidationHandler の実装パターンは、次のセクション「実装でハマりやすい技術的落とし穴」で詳しく取り上げます。