🛰️

【iOS】Core NFC応用 - ネイティブアクセス編

2024/04/30に公開

これまでの記事でNDEFメッセージの読み書きを行う方法について説明しました。

https://zenn.dev/naoya_maeda/articles/927aaf68dac33d
https://zenn.dev/naoya_maeda/articles/f2892d3165e2c1

本記事では、iOS 13で追加された通信プロトコルでNFCタグにアクセスする方法について説明します。

Core NFCがカバーしているプロトコル

iOS 13以降では、NDEFメッセージの読み書きに加え、新たに4つの通信プロトコルを使用したNFCタグへのアクセスに対応しました。

  • ISO7816
  • ISO18092 (FeliCa)
  • ISO14443 (MIFARE)
  • ISO15693

新たに追加された通信プロトコルに準拠したNFCタグの検出

NDEFフォーマットに対応したNFCタグの検出はNFCNDEFReaderSession を使用していましたが、新たに追加された通信プロトコルに準拠したNFCタグの検出は、NFCTagReaderSession を使用します。
init(pollingOption:delegate:queue:)NFCTagReaderSession オブジェクトを初期化します。pollingOption 引数には、NFCTagReaderSession.PollingOption で通信プロトコルを指定します。NFCTagReaderSession.PollingOption は以下のように定義されています。

他の引数にはNFCタグ検出時にコールされるデリゲートメソッドの委譲先、デリゲートメソッドがディスパッチされるキューを指定します。alertMessage でNFCタグ検出処理が開始する際に表示されるハーフモーダルに表示するメッセージを指定します。

begin()でセッションが開始し、NFCタグ検出処理が行われます。この時、iPhoneをNFCタグにかざすことを促すハーフモーダルが表示されます。

以降の章では、ISO18092 (FeliCa)プロトコルで、交通系ICカード (Suica)にアクセスしてIDmを取得する方法、ISO14443 (MIFARE)プロトコルで、NFCタグにアクセスする方法について説明します。

交通系ICカード (Suica)のIDmを取得する方法

info項目の編集

info項目一覧のKeyに「ISO18092 system codes for NFC Tag Reader Session」を追加し、Arrayの要素にString型で「0003」を追加します。

この「0003」は「システムコード」と呼ばれ、企業 / 団体毎あるいはアプリケーション毎に割り当てられている認証コードです。

交通系ICカード (Suica)の検出

init(pollingOption:delegate:queue:)NFCTagReaderSession オブジェクトを初期化します。pollingOption 引数には、検出するNFCタグが準拠するプロトコルを表すNFCTagReaderSession.PollingOption 、NFCタグ検出時にコールされるデリゲートメソッドの委譲先、デリゲートメソッドがディスパッチされるキューを指定します。
以下のコードでは、ISO18092 (FeliCa)プロトコルに準拠したNFCタグを検出するためのNFCTagReaderSession オブジェクトを生成しています。

交通系ICカード (Suica)を検出する時は、pollingOption.iso18092 を指定します。

NFCTagReaderSessionDelegate プロトコル

NFCTagReaderSession のイニシャライザで指定した委譲先のクラスを、NFCTagReaderSessionDelegate プロトコルに準拠させます。NFCTagReaderSessionDelegate プロトコルには、NFCタグ検出時の様々なリーダーセッションイベントを処理するためのデリゲートメソッドが定義されています。
NFCタグ検出後の処理を実装する前に、NFCTagReaderSessionDelegate プロトコルで定義されているデリゲートメソッドを確認しましょう。

tagReaderSessionDidBecomeActive(_:)
tagReaderSession(_:didDetect:)
tagReaderSession(_:didInvalidateWithError:)

tagReaderSessionDidBecomeActive(_:)

リーダーセッションがアクティブになった時にコールされるメソッドです。

tagReaderSession(_:didDetect:)

リーダーセッションがNFCタグを検出した時にコールされるメソッドです。このメソッド内でNFCタグに書き込まれているデータの読み取り/書き込み機能を実装します。

tagReaderSession(_:didInvalidateWithError:)

リーダーセッションが無効になった時にコールされるメソッドです。何らかのエラーが発生し、リーダーセッションが中断された時や完了した時にコールされます。

タグをリーダーセッションに接続

NFCTagReaderSessionconnect(to:completionHandler:) でタグをリーダーセッションに接続します。

タグの取得

NFCTag オブジェクトを利用してNFCFeliCaTag オブジェクトを取得します。

NFCFeliCaTag オブジェクトのcurrentIDm でFeliCaのIDmを取得することができます。

ISO14443 (MIFARE)プロトコルでNFCタグにアクセスする方法

init(pollingOption:delegate:queue:)NFCTagReaderSession オブジェクトを初期化します。pollingOption 引数には、検出するNFCタグが準拠するプロトコルを表すNFCTagReaderSession.PollingOption 、NFCタグ検出時にコールされるデリゲートメソッドの委譲先、デリゲートメソッドがディスパッチされるキューを指定します。
以下のコードでは、ISO14443 (MIFARE)プロトコルに準拠したNFCタグを検出するためのNFCTagReaderSession オブジェクトを生成しています。

ISO14443 (MIFARE)プロトコルに準拠したNFCタグを検出する時は、pollingOption.iso14443 を指定します。

タグの取得

NFCTag オブジェクトを利用してNFCMiFareTag オブジェクトを取得します。MIFAREはいくつかの規格が存在しており、それぞれメモリ構成、セキュリティ面、価格が異なります。以下のコードでは、UltralightタイプのNFCMiFareTag オブジェクトを取得しています。

タグをリーダーセッションに接続

NFCTagReaderSessionconnect(to:completionHandler:) でタグをリーダーセッションに接続します。

MIFAREプロトコルでタグにアクセス

MIFAREプロトコルでタグにアクセスするためには、sendMiFareCommand(commandPacket:completionHandler:) を使用します。commandPacket には読み込み用または書き込み用のコマンドを指定します。

データの読み込み

読み込み用のコマンド

読み込み用のコマンドは0x30 です。第1要素は0x30、第2要素は読み込み先ブロックのアドレスを格納したUInt8型を要素とする配列を、Data 型でキャストしたものを用意します。例えば、以下のコマンドは5ブロック目のデータを読み込むコマンドになります。

  • Data([0x30, 0x05])

用意したコマンドを、sendMiFareCommand(commandPacket:completionHandler:)commandPacket 引数に指定します。例えば、以下を実行すると、5ブロック目のデータを読み込む処理が実行されます。

  • sendMiFareCommand(commandPacket: Data([0x30, 0x05]))

読み込み処理

NFCMiFareTag オブジェクトを取得した後、sendMiFareCommand(commandPacket:completionHandler:)を実行して読み込んだデータを表すData オブジェクトを取得します。取得したData オブジェクトをString オブジェクトに変換することで、NFCタグに書き込まれていた文字列を取得することができます。

データの書き込み

書き込み用のコマンド

書き込み用の固定コマンドは0xA2 です。第1要素は0xA2、第2要素は書き込み先ブロックのアドレスを格納したUInt8型を要素とする配列を、Data型でキャストしたものに対して、書き込みたいデータを表すData 型オブジェクトを加えたものを用意します。例えば、以下のコマンドは5ブロック目にData 型のwriteData を書き込むコマンドになります。

  • Data([0xA2, 0x05]) + writeData

用意したコマンドを、sendMiFareCommand(commandPacket:completionHandler:)commandPacket 引数に指定します。例えば、以下を実行すると、5ブロック目にデータを書き込む処理が実行されます。

  • sendMiFareCommand(commandPacket: Data([0xA2, 0x05]) + writeData)

書き込みデータの準備

NFCタグに書き込みたい文字列をData 型オブジェクトにキャストします。sendMiFareCommand(commandPacket:completionHandler:) は1ブロックずつ書き込み処理を行うため、NFCタグに書き込むData 型オブジェクトblockData のサイズは1ブロックサイズにする必要があります。MIFARE - Ultralightタイプのタグは、1ブロック4byteとなっているため、blockData のサイズも4byteにします。
サンプルコードでは、String 型のwriteMesage のうち先頭4文字分をData 型にキャストすることで、4byteのData 型オブジェクトblockData を生成しています。

もし、writeMesage に格納されている文字列が4文字未満 (4byte未満)だった時、空のData 型オブジェクトをblockData に追加することで、blockData のサイズを4byteにしています。

書き込み処理

書き込みコマンドを、sendMiFareCommand(commandPacket:completionHandler:)commandPacket 引数に指定して書き込み処理を行います。書き込みに成功した時、返ってきたData 型オブジェクトの1byte目に0x0Aが格納されています。

今回は4byte文字列の読み書きを行いました。4byteよりも大きなサイズのデータを読み書きを行う時は、複数ブロックに跨ってデータの読み書きを行います。また、MIFARE - Ultralightタイプのタグは1ブロックあたりのサイズが4byteですが、1ブロックあたりのサイズが16byteのものもあります。MIFAREプロトコルでデータの読み書きを行う時は、使用するタグのタイプをチェックするようにしましょう。

サンプルソース

ISO18092 (FeliCa)

https://github.com/NAOYA-MAEDA-DEV/CoreNFCSampler

ISO14443 (MIFARE)

https://github.com/NAOYA-MAEDA-DEV/CoreNFCWithMifare

参考資料

・Core NFC Enhancements
https://developer.apple.com/videos/play/wwdc2019/715/
・What's new in Core NFC
https://developer.apple.com/videos/play/wwdc2020/10209/

Discussion