Core NFCのアップデート
Core NFCはiOS 11で追加されたフレームワークですが、iOS 13で大幅に強化されました。何が変わったのかを知るために、まずは「以前はどうだったのか」というところから解説していきます。
これまでのCore NFC
おさらい:Core NFCの基本的な実装方法
iOS 11から使えるようになったCoer NFCフレームワークでは、NDEF(NFC Data Exchange Format)というデータフォーマットを読み取ることができました。おさらいとして、その実装手順を簡単に解説します。
まず、.entitlements
ファイルに次の項目を追加します。
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>NDEF</string>
</array>
CoreNFCをimportして、
import CoreNFC
NFCNDEFReaderSession
というのを初期化して、beginでセッションを開始すると、
let session = NFCNDEFReaderSession(delegate: self, queue: nil,
invalidateAfterFirstRead: false)
session.begin()
例のハーフモーダルなNFCスキャン画面が立ち上がります。
ここで、次のようにNFCNDEFReaderSessionDelegate
プロトコルに準拠した実装をしておけば、
extension ViewController: NFCNDEFReaderSessionDelegate {
func readerSession(_ session: NFCNDEFReaderSession,
didDetectNDEFs messages: [NFCNDEFMessage]) {
// do something
}
func readerSession(_ session: NFCNDEFReaderSession,
didInvalidateWithError error: Error) {
// do something
}
}
NFCタグを読み取った際にNFCNDEFMessage
オブジェクト(またはエラー)を受け取ることができます。
以前のCore NFCではできなかったこと
幻の読み出しセッション
iOS 11からNFCISO15693ReaderSession
というクラスと、そのセッションからのイベントをハンドリングするためのNFCReaderSessionDelegate
というプロトコルが用意されてはいたのですが、それに対応する"com.apple.developer.nfc.readersession.formats"のentitlementsの値が公開されていなかったため、実質的に利用不可でした。
iOS 12になってもこのクラスは使えないまま存在していました。
NDEFの書き込み
また、NDEFを読み取ることはできるものの、書き込む機能は提供されていませんでした。読み出し機能を使おうにもまずそのNDEFデータをNFCタグに書き込む必要があるので、別途書き込み用にAndroid端末を用意したりしていました。
iOS 12になっても、書き込み機能は提供されないままでした。
iOS 13での追加機能
上述したように非常に限定された機能しか持たないまま登場し、iOS 12でもほとんど改善されなかったCore NFCですが、iOS 13では一気に前進しました。
多くのNFCタグが読み出し可能に
これまでクラスは用意されていたものの使えないままだったISO 15693だけでなく、MiFareやFeliCaといった世界や日本国内で普及しているNFCタグ規格の読み出しがサポートされました。
- ISO 7816
- MiFare(ISO 14443)
- ISO 15693
- FeliCa (ISO 18092)
これらの読み出しにはNFCTagReaderSession
という新クラスを用いることになり(後述します)、iOS 11から存在はしていたが使えなかったクラスNFCISO15693ReaderSession
はDeprecatedどころか、フレームワークから消えてしまいました。
NDEFの書き込みが可能に
NDEFの書き込みもついにサポートされました。これでついに書き込み・読み出しの両方がiOSデバイスでできるようになったわけです。
新たに読めるようになったNFCタグ規格の概要
WWDC19の当該セッション[1]のスライド冒頭には、以下の規格について新たにサポートしたと書いてあります。
- ISO 7816
- ISO 14443
- ISO 15693
- ISO 18092
そして、iOS 13でCore NFCに新規追加されたNFCNDEFTagプロトコルを継承するプロトコル群を見ると、次のような種類があります。
- NFCFeliCaTag
- NFCISO15693Tag
- NFCISO7816Tag
- NFCMiFareTag
ISO ◯◯といった規格と、MiFareやFeliCaといった一種のブランドネーム的な名前との対応関係がよくわからなかったのと、具体的な用途を知りたかったので、簡単に調べてみました。
ISO 7816
- 接触型
- ISO 7816では最小部分しか規格化されていないので、業界やサービスに特化した仕様が作成されている。
主な用途
- クレジットカード
- キャッシュカード
- 住民基本台帳カード
- B-CASカード
- UIMカード(SIMカード)
ISO 14443 - MiFare
- オランダのPhilipsが開発した規格
- 世界で最も普及している近接型ICカード
- 通信速度は106kbps
主な用途(国内)
- NTTのICテレホンカード
- タバコ購入用成人認証カードtaspo
- マスターカードの非接触カード決済方式のPayPass
ISO 15693
- 近傍型(Vincinity)非接触ICカードの国際標準規格
- ISO/IEC 14443と同様に13.56MHzの周波数を利用
- 通信距離は約1mから1.5m程度までだが、規格上の想定は70cm程度
主な用途
- フリーゲートシステムや物流の商品タグ
- 図書館IC
ISO 18092 - FeliCa
- ソニーが開発した近接型ICカード
- 日本国内では圧倒的なシェア
- 通信速度は212kbps
- Philipsと組んで非接触ICカード用の通信方式「Near Field Communication(NFC)」としてISOに提案し、2003年にISO/IEC 18092として承認
主な用途
- Suicaなどの交通系カード
- Edyなどの電子マネー
実装: Suica/PASMOを読む
ここからは、iOS 13で新たに追加されたクラス・プロトコル群を使用して、Suica/PASMOを読む実装の手順を紹介します。
1. entitlementsの準備
.entitlements
ファイルに以下を追加します。
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>TAG</string>
</array>
NDEF読み出しとタグ読み出しの両方を追加する場合のentitlements
ファイルは次のようになります。
2. FeliCaシステムコードを登録する
事業者/使用目的ごとに割り当てられている「FeliCaシステムコード」という2バイトの値があります。FeliCaタグをCore NFCで読み取るには、そのFeliCaタグのシステムコードをInfo.plistのcom.apple.developer.nfc.readersession.felica.systemcodes
キー(項目名は"ISO18092 system codes for NFC Tag Reader Session")に追加します。
たとえばSuica/PASMOなどの交通系のシステムコードは0003
という値なので[2]、Info.plistへは次のように追加します。
<key>com.apple.developer.nfc.readersession.felica.systemcodes</key>
<array>
<string>0003</string>
</array>
3. セッションを作成し、開始する
NFCTagReaderSession
オブジェクトを初期化します。イニシャライザは次のように定義されています。
convenience init?(pollingOption: NFCTagReaderSession.PollingOption,
delegate: NFCTagReaderSessionDelegate,
queue: DispatchQueue? = nil)
第1引数にはポーリングシーケンス内で検出すべきタグの種類を指定します。
実装は次のようになります。
var session: NFCTagReaderSession?
session = NFCTagReaderSession(pollingOption: .iso18092, delegate: self)
ここではFeliCaタグを読み出したいので、イニシャライザの第1引数には.iso18092
を指定しています。
begin()
メソッド[3]を呼び、読み出しセッションを開始します。
session?.begin()
4. NFCTagReaderSessionDelegateを実装する
NFCタグの読み出しイベントを受け取るため、NFCTagReaderSessionDelegate
プロトコルに準拠し、各メソッドを実装します。
すべてのメソッドがrequiredです。
extension ViewController: NFCTagReaderSessionDelegate {
// セッションがアクティブになると呼ばれる
func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
// do something
}
// エラー発生時に呼ばれる
func tagReaderSession(_ session: NFCTagReaderSession,
didInvalidateWithError error: Error) {
// do something
}
// タグ検出時に呼ばれる
func tagReaderSession(_ session: NFCTagReaderSession,
didDetect tags: [NFCTag]) {
// do something
}
}
5. タグに接続する
NFCTagReaderSessionDelegate
のtagReaderSession(_:didDetect:)
メソッドの引数から、検出したタグのリストがNFCTag
の配列として得られます。
これをNFCTagReaderSession
のconnect(to:)
メソッドに渡してタグに接続します。
func tagReaderSession(_ session: NFCTagReaderSession,
didDetect tags: [NFCTag]) {
let tag = tags.first!
session.connect(to: tag) { error in
// do something
}
}
6. 接続したタグからデータを読み出す
タグへの接続に成功すると、データを読み出せるようになります。
NFCTag
は次のようなenum型として定義されているため、
public enum NFCTag {
case feliCa(NFCFeliCaTag)
case iso7816(NFCISO7816Tag)
case iso15693(NFCISO15693Tag)
case miFare(NFCMiFareTag)
public var isAvailable: Bool { get }
}
NFCFeliCaTag
プロトコルに準拠するオブジェクトを次のように取り出し、
guard case .feliCa(let felicaTag) = tag else { return }
そこからIDmやFeliCaシステムコードといった情報を読み出すことができます。
print("IDm: " + felicaTag.currentIDm.hexString())
print("System Code: " + felicaTag.currentSystemCode.hexString())
実際に手元のPASMOを読んでみたところ、次のような結果が得られました。(IDmの読み出し結果については割愛)
System Code: 0003
NFCFeliCaTag
にはFeliCaのさまざまなコマンドを呼び出すためのメソッドが定義されています。これらを駆使してFeliCaタグからより詳細な情報を読み出せるわけですが、コマンドを呼び出すための詳細はSuicaやPASMOといったそれぞれのシステムで定義されており、それらの多くは非公開なので本書では取り扱いません。[4]
Discussion