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

これまでの記事でNDEFメッセージの読み書きを行う方法について説明しました。
本記事では、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:)
リーダーセッションが無効になった時にコールされるメソッドです。何らかのエラーが発生し、リーダーセッションが中断された時や完了した時にコールされます。
タグをリーダーセッションに接続
NFCTagReaderSession のconnect(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 オブジェクトを取得しています。
タグをリーダーセッションに接続
NFCTagReaderSession のconnect(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)
ISO14443 (MIFARE)
参考資料
・Core NFC Enhancements ・What's new in Core NFC
Discussion