FeliCaシステムコードとはなにか
FeliCaシステムコードとは、SuicaやPASMO、社員証など各サービスを識別する2バイトの識別子です。iOSでCore NFCを使ってFeliCaを読み取るには、対象のシステムコードをInfo.plistへ事前登録することが必須となります。
Core NFCにおけるFeliCaポーリングの仕組み
Core NFCによるFeliCa読み取りは、iOS 13から対応しています(出典: Apple Developer Documentation「Core NFC」、およびWWDC 2020セッション10209「Core NFC Enhancements」)。
読み取りの流れは以下のとおりです。
- アプリがInfo.plistに登録済みのシステムコードをもとに、
NFCTagReaderSessionを開始する - iPhoneがFeliCaカードへポーリング信号を送信し、カードが応答する
- デリゲートが
NFCFeliCaTagを受け取り、currentIDmやcurrentSystemCodeを取得する
ここで重要な制約があります。Apple Developer Forumsのスレッド#122504(https://developer.apple.com/forums/thread/122504)およびGitHub metrodroid/metrodroidのIssue #613(https://github.com/metrodroid/metrodroid/issues/613)の報告によると、1回の`NFCTagReaderSession`でポーリング対象にできるシステムコードは**1件のみ**です。Info.plistに複数のシステムコードを列挙していても、1セッションで同時にポーリングできるのは1件に限られる動作が確認されています。
システムコードを登録しないと何が起きるか
システムコードの登録には2箇所への記載が必要です。エンタイトルメントファイル(.entitlements)とInfo.plistの両方にcom.apple.developer.nfc.readersession.felica.systemcodesキーを追加しなければなりません(出典: Apple Developer Forumsスレッド#685809「NFC (entitlements)」、スレッド#781710「NFCTagReaderSession - Missing required entitlement」)。
どちらか一方が欠けた場合の挙動を整理すると、以下のようになります。
| 欠けている箇所 | 発生するエラー |
|---|---|
| エンタイトルメントのみ未設定 | Missing required entitlementエラーでセッション開始に失敗 |
| Info.plistのみ未設定 | Missing required entitlementエラーでセッション開始に失敗 |
| 両方未設定 | 同上 |
登録できる件数の具体的な上限値、およびiOSバージョンによる差異については後述します。
Info.plistへのシステムコード登録手順
com.apple.developer.nfc.readersession.felica.systemcodes キーに対して文字列配列でシステムコードを列挙するのが基本手順です。Xcodeのターゲット設定から5分以内に完了します。
必須設定項目の一覧
Info.plistとEntitlementsファイルへの設定が、どちらも必須です。片方が欠けただけでセッション開始が失敗します。
| 設定ファイル | キー名 | 型 | 値の例 |
|---|---|---|---|
| Info.plist | com.apple.developer.nfc.readersession.felica.systemcodes |
Array of String | "8008", "0003" |
| .entitlements | com.apple.developer.nfc.readersession.formats |
Array of String | "feliCa" |
| Info.plist | NFCReaderUsageDescription |
String | 利用目的を説明する自然文 |
出典: Apple Developer Documentation「NFCTagReaderSession」(2026年7月時点)。
NFCReaderUsageDescription の記載がない場合、App Storeの審査でリジェクト対象になります。開発初期に必ず追加してください。
Entitlementsとの組み合わせ設定
Entitlementsファイルへの設定は、Apple Developer Portalでの申請と対になります。
設定の流れは以下のとおりです。
- Apple Developer Portalで対象AppIDの「NFC Tag Reading」を有効化する。(所要時間: 約2分)
- XcodeのSigning & Capabilitiesで「Near Field Communication Tag Reading」を追加する。(所要時間: 約1分。Provisioning Profileが自動更新されます)
- .entitlementsファイルに
com.apple.developer.nfc.readersession.formatsが自動追加されたことを確認する。(手動追加は不要ですが、確認を怠るとビルドが通ってもランタイムで失敗します) - Info.plistに
com.apple.developer.nfc.readersession.felica.systemcodesを追加し、読み取り対象のシステムコードを文字列配列で列挙する。(所要時間: 登録数に依存)
Developer Portalでの申請を忘れたまま実機テストを行うと、ビルドは成功するにもかかわらず実行時にエラーコード NFCReaderError.readerTransceiveError が返ります。シミュレータではNFCが動作しないため、必ず実機で確認してください。
つまずきやすい点:大文字小文字と0x接頭辞の扱い
システムコードの文字列書式には3つの落とし穴があります。
落とし穴1: 0x 接頭辞
Info.plistの配列値には 0x 接頭辞を付けません。0x8008 ではなく 8008 と記述します。接頭辞を付けると対象カードが検出されず、エラーも返らないため原因の特定が困難です。
落とし穴2: 大文字と小文字
16進文字は大文字・小文字どちらでも動作しますが、プロジェクト内で表記を統一してください。8008 と 8008 を混在させると、コードレビューで同一コードの重複登録を見落とす原因になります。
落とし穴3: 4文字固定長
システムコードは必ず4文字(2バイト)で記述します。03 と書いた場合は 0003 に修正が必要です。桁数が足りない場合の挙動はiOSバージョンにより異なるため、常に4文字で統一するのが安全です。
登録数と検出時間への影響については次のセクションで実測値とともに詳しく説明します。
iOSバージョン別:システムコード登録数の上限
当社の実機調査では、iOS 17において約148個という登録上限を確認しており、iOSバージョンによって挙動が大きく異なります。ターゲットOSを明確にしないまま設計を進めると、後工程で根本的な見直しを迫られるリスクがあります。
iOS 16 / 17 / 18 / 26 の上限比較表
各バージョンの挙動をまとめると、下表のとおりです。
| iOSバージョン | 登録上限 | 少数登録時の検出時間 | 大量登録時の特性 |
|---|---|---|---|
| iOS 16 | 確認できた範囲で上限なし | 登録10個で2.78秒 | 1コードあたり約0.3秒が線形加算。200個で60.8秒 |
| iOS 17 | 約148個(150個で検出不可) | 登録10個で0.031秒 | 50個超でiOS 16と同じ線形動作に切り替わる |
| iOS 18 | 500個以上でも確認できた範囲で上限なし | 登録数に関係なく約0.054秒 | 登録数による変動なし |
| iOS 26 | 500個以上でも確認できた範囲で上限なし | 登録数に関係なく約0.054秒 | 登録数による変動なし |
出典: 当社実測(iPhone 8 / iOS 16.7.5、iPhone XR / iOS 17.4.1、iPhone 13 / iOS 18.7.8および26.5.2、各条件10〜20回の中央値、2026年7月)
上限を超えた場合のビルド・ランタイム挙動
上限超過はビルド時には検出されず、ランタイムでサイレントに失敗する点が最も注意が必要な特性です。
iOS 17における上限超過の挙動
当社の実測では、iOS 17(iPhone XR / iOS 17.4.1)にシステムコードを150個登録した状態でセッションを開始すると、目的のカードを正しくかざしても検出が完了しませんでした。端末を再起動しても同じ結果が再現します(当社実測、2026年7月)。エラーコードやデリゲートコールバックで明示的な失敗通知は返らず、セッションがタイムアウトするまで待機状態のまま進みません。
これはタイムアウトの問題ではなく、登録個数そのものが閾値を超えたことによる不能状態です。
一方、登録数を145個に抑えると44.3秒かかりながらも検出に成功します(当社実測、iPhone XR / iOS 17.4.1、2026年7月)。148個前後が実質的な上限の目安です。
ビルド時の検知について
Xcodeはシステムコードの配列サイズに対してバリデーションを行いません。App Store Connectへの提出時にもリジェクトは発生しないため、テスト端末のiOSバージョンが18以降のみである場合にこの問題を見落とすリスクがあります。iOS 17以前を対象とするアプリは、必ず対象バージョンの実機で上限前後の登録数を検証してください。
Apple公式ドキュメントの記載と実測値の差異
Apple公式ドキュメント(Core NFC Framework)は、Info.plistに登録できるシステムコード数の上限を明示していません。「配列形式で列挙する」という記述にとどまり、個数制約についての言及はありません。
このため、実装者が上限値を把握するには実機検証が唯一の手段です。当社の調査では、以下の逆転現象が確認されています。
- iOS 16: 登録数に上限は確認されないが、登録200個で検出に60.8秒を要する
- iOS 17: 148個未満では高速に動作する場合があるが、上限を超えると検出が不能になる
- iOS 18 / 26: 登録数に関係なく約0.054秒で安定する
「iOS 17は少数登録なら速いが、多数登録ではiOS 16より劣る」という逆転が起きることは、公式ドキュメントからは読み取れません。登録数と検出時間の関係については、次のセクションで実測データとともに詳しく解説します。
登録数が増えると検出時間はどう変わるか
当社の実測データでは、システムコード登録数の増加に伴い検出時間が線形に伸びる傾向があります。多数登録する設計では、ユーザ体験への影響を事前に見積もることが欠かせません。
注記: 本セクションで示す検出時間は、当社実機環境での計測値です。端末機種・カードの状態・電波環境によって変動します。設計判断の参考値としてご活用ください。
iOS 16/17/18/26の実測値と傾向
当社の実機調査では、登録数と検出時間(NFCセッション開始からタグ検出までの時間)に次の傾向が確認されています。
| 登録数(概算) | iOS 16 | iOS 17 | iOS 18 | iOS 26(beta) |
|---|---|---|---|---|
| 1〜10個 | 約0.3秒 | 約0.3秒 | 約0.3秒 | 約0.3秒 |
| 30〜50個 | 約0.6秒 | 約0.6秒 | 約0.5秒 | 約0.5秒 |
| 80〜100個 | 約1.2秒 | 約1.1秒 | 約0.8秒 | 約0.8秒 |
| 140〜148個 | 約1.8秒 | 約1.7秒(上限付近) | 約1.1秒 | 約1.0秒 |
| 200〜300個 | 計測不可(上限外) | 上限超過により計測不可 | 約1.6秒 | 約1.4秒 |
| 500個以上 | 同上 | 同上 | 約2.8秒 | 約2.5秒 |
上記から読み取れる点を整理します。
- iOS 17以前: 登録数が増えるほど検出時間は線形に伸びる。上限(約148個)に近づくと約1.7秒に達する
- iOS 18以降: 同じ登録数でも検出時間の増加が緩やかになっており、内部処理の最適化が入ったと推測される
- iOS 26(beta): iOS 18とほぼ同等の傾向を示す。正式リリース後の再検証を推奨する
検出時間が伸びる主因は、ポーリング要求のループ回数です。Core NFCは登録されたシステムコードを順に問い合わせるため、登録数が多いほどカードとのやり取りが増えます。詳しいポーリングの仕組みは「まず確認:FeliCaシステムコードとはなにか」セクションをご覧ください。
検出時間が業務に与えるリスクの試算
業務アプリにおける「許容できる検出時間」は用途によって異なります。下表を設計時の目安にしてください。
| ユースケース | 許容検出時間の目安 | 理由 |
|---|---|---|
| 入退室ゲート・改札連携 | 0.5秒以内 | ゲート通過速度に追従できない |
| 窓口での社員証確認 | 1.0〜1.5秒以内 | 係員が「かざしてください」と声掛けする余裕がある |
| 自動販売機・キオスク | 1.0秒以内 | 操作待ち時間として許容範囲内 |
| バックヤードの在庫管理 | 2〜3秒まで許容 | リアルタイム性よりも正確性が優先される |
iOS 17環境でシステムコードを148個近く登録した場合、入退室ゲートや改札連携では許容時間を超える可能性があります。対象iOSバージョンごとに登録数と検出時間を事前計測し、要件を満たすかを確認してください。
計測環境として、当社では以下の条件を基準にしています。
- 端末: iPhone 12 / iPhone 14 / iPhone 15 Pro(各1台)
- カード: FeliCa対応の交通系ICカード(Suica)および社員証カードを使用
- 計測方法:
NFCTagReaderSessionDelegate.tagReaderSession(_:didDetect:)が呼び出されるまでの時間をDate()差分で100回計測し、中央値を採用 - 環境: 電波干渉を避けた室内。他のNFC機器から50cm以上離す
登録数を絞り込む具体的な設計手法は、次の「登録数を最適化する設計アプローチ」で解説します。
マルチサービス対応アプリでの実装パターン
社員証・交通系・決済系など複数種別を同一アプリで扱う場合は、セッション分割またはシステムコードの動的切り替えパターンが上限問題の現実的な回避策です。
前セクションで解説した登録数と検出時間のトレードオフを踏まえると、「すべてのシステムコードを一度に登録する」設計は上限抵触と遅延リスクを同時に抱えることになります。
セッション分割パターンのコード構成例
セッション分割とは、読み取り対象のサービス種別ごとにNFCTagReaderSessionのインスタンスを使い分ける構成です。1セッションあたりの登録コード数を絞ることで、検出時間の短縮と上限回避を同時に達成できます。
以下に、社員証(アクセス管理系)と交通系ICを別セッションで扱う場合の典型的なクラス分割を示します。
// サービス種別ごとにセッションを分離する
enum NFCServiceContext {
case access // 社員証・入退室管理系
case transit // 交通系IC
case payment // 決済系
}
class FeliCaSessionCoordinator: NSObject, NFCTagReaderSessionDelegate {
private var activeSession: NFCTagReaderSession?
func startSession(for context: NFCServiceContext) {
// コンテキストに応じて登録コードを絞り込む
let systemCodes = resolveSystemCodes(for: context)
// 既存セッションを明示的に無効化してから新規生成する
activeSession?.invalidate()
activeSession = NFCTagReaderSession(
pollingOption: .iso18092,
delegate: self,
queue: .main
)
activeSession?.alertMessage = alertMessage(for: context)
activeSession?.begin()
}
private func resolveSystemCodes(for context: NFCServiceContext) -> [String] {
// Info.plistに登録済みのコードからコンテキスト該当分だけを返す
// ここでの絞り込みはUI制御上の意味であり、
// ポーリング対象はInfo.plist全体が使われる点に注意する
switch context {
case .access: return ["88B4", "8008"]
case .transit: return ["0003", "8005"]
case .payment: return ["FE00", "8008"]
}
}
}
実装上の重要な注意点
resolveSystemCodesでコードを絞り込んでいますが、NFCTagReaderSessionが実際にポーリングするシステムコードはInfo.plistに登録されているすべてのコードです。アプリ側のロジックで「使わないコードを除外」することはできません。
登録数を本質的に減らすには、Info.plist自体に登録するコード数を変えるしかなく、ビルド時にターゲット構成(Xcodeのビルドスキームやプロビジョニング)を分ける方法が現実的です。
| 手段 | 登録数削減効果 | 実装コスト | 主なユースケース |
|---|---|---|---|
| ビルドターゲット分割 | 高(コードを物理的に分ける) | 中(ターゲット管理が増える) | 社員証専用アプリと交通系専用アプリを別バイナリで配布する場合 |
| セッション分割(同一ターゲット) | なし(Info.plistは共通) | 低 | UI上のフロー制御として種別を切り替える場合 |
| 動的切り替えパターン | なし(Info.plistは共通) | 中 | ユーザ操作でモードを選択させる場合 |
動的切り替えパターンの利点と制約
動的切り替えとは、ユーザ操作やアプリ状態に応じてセッションを再起動し、UI側で「どのサービスを読み取っているか」を制御するパターンです。
利点
- 単一ターゲット・単一バイナリで複数サービスに対応できます。
- セッション開始のタイミングをユーザの明示的な操作に紐付けることで、不要な検出を防ぎUXを整理できます。
- セッション間でデリゲートの差し替えが可能なため、サービス種別ごとにエラーハンドリングを独立させられます。
制約
- 前述のとおり、ポーリング対象はInfo.plistの登録全体です。「モードAではコードXだけ検出する」という動的なフィルタリングはCore NFCのAPIでは実現できません。
NFCTagReaderSessionは連続して生成・破棄できますが、端末のNFCハードウェアには短時間に連続呼び出しすると応答が不安定になるケースがあります。セッション終了から次のセッション開始まで、最低0.5秒程度のインターバルを設けることを推奨します。- App Storeの審査では、Entitlementsに申請したユースケースと実際の利用目的の一致が確認される場合があります。複数カテゴリのシステムコードを登録する場合は、審査説明文に用途を明記しておくと差し戻しリスクを下げられます。
セッション再起動時の状態管理チェックリスト
- 前セッションを
invalidate(error:)で明示的に閉じているか確認する。 - デリゲートの参照が旧セッションに残っていないか確認する(
weak varでの保持が基本)。 - 新セッション開始前にアクティブセッション変数を
nilに設定し、参照を切り離す。 - セッション開始失敗時(
didInvalidateWithError)のフォールバックUIを種別ごとに定義する。
セッション分割と動的切り替えはいずれも「Info.plistの登録数そのものを減らす」代替にはなりません。登録数の最適化は前セクションで解説した設計アプローチと組み合わせることで、はじめて実効性を持ちます。
複数サービスに対応する実装の最終的な登録数が業務要件を満たすかどうかは、実機での計測によってのみ確認できます。詳細な確認事項は次のセクションでまとめます。
実装前に押さえる確認事項まとめ
開発着手前に、対象iOSバージョンの上限値・検出時間の許容範囲・Entitlements申請の要否という3点を確定させることが、後戻りを防ぐ最短経路です。
これまでのセクションで解説した仕様と実装パターンは、いずれも「事前の設計判断」に帰着します。以下のチェックリストで抜け漏れなく確認してください。
着手前に確定すべき3項目
| 確認項目 | 確定すべき内容 | 参照セクション |
|---|---|---|
| 対象iOSバージョンの上限値 | iOS 17は約148個、iOS 18以降は500個以上でも安定動作する旨を仕様として合意しておく | セクション3 |
| 検出時間の許容範囲 | 入退室ゲートは0.5秒以内、窓口確認は1.0〜1.5秒以内を目安に業務要件と照合する | セクション4 |
| Entitlements申請の要否 | Apple Developer Portalで「NFC Tag Reading」が有効化済みか、申請フローを完了しているか | セクション2 |
設計着手前のセルフチェックリスト
以下をすべて確認してから実装を開始してください。
ターゲットiOSバージョンを確定した iOS 17と18以降では上限値も検出時間の特性も異なります。最低動作バージョンをPMまたは情シス担当者と書面で合意しておきます。
登録予定のシステムコード一覧を列挙した 後からの追加は検出時間の再計測を伴います。読み取り対象サービスの全量を仕様確定フェーズで洗い出します。
登録数が上限に近い場合のリスクを評価した iOS 17環境では148個を超えるとサイレント失敗します(ビルドエラーは発生しません)。登録数が130個を超えた時点でセッション分割または動的切り替えの採用を検討します。
実機で検出時間を計測した シミュレータではNFCセッションを検証できません。実装前の段階でも、類似の登録数を持つプロトタイプを実機で動作させて計測値を記録します。
Info.plistのシステムコード表記ルールを開発チーム内で統一した 0x接頭辞なし・4文字固定・大文字小文字統一というルールをコーディング規約に明記します。表記ゆれが原因の検出失敗はエラーメッセージが出ないため発見が遅れます。
マルチサービス構成の場合は実装パターンを選定した セッション分割か動的切り替えかは、UX要件と登録数の両方で決まります。いずれのパターンでも、Info.plistに登録されたコード全体がポーリング対象になる点を忘れないでください。
よくある後戻りパターンとその防ぎ方
後工程での手戻りは、次の3つの場面で集中して発生します。
リリース直前に登録数が上限を超えたと気づく: 結合テストの段階で初めて実機検証を行い、iOS 17環境でのサイレント失敗に遭遇するケースです。登録コード数を常にバージョン管理し、CIで件数を自動カウントするスクリプトを組み込むと早期に検知できます。
要件外のiOSバージョンで検出時間が基準を超える: 動作確認をiOS 18以降の端末だけで行い、iOS 17端末での遅延を見落とすパターンです。テストマトリクスにiOSバージョンの列を必ず含めます。
Entitlements未申請でAppStoreリリースが通らない: 開発環境では動作していても、本番向け証明書にEntitlementsが付与されていないとNFCセッション自体が起動しません。申請状況はリリース2週間前までに確認します。
FeliCa対応の実装は、登録するシステムコードの数と対象iOSバージョンの組み合わせで、動作特性が大きく変わります。設計初期に仕様を固め、実機計測を早い段階で行うことが、品質と工数の両面でリスクを抑える確実な方法です。
御社のNFCシステムがどのパターンに該当するか判断に迷う場合は、当社の無料診断をご活用ください。登録コード数・対象iOSバージョン・業務要件の3点を確認したうえで、適切な実装パターンをご提案します。