🏷️

Core NFCのアップデート

2023/11/07に公開

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. タグに接続する

NFCTagReaderSessionDelegatetagReaderSession(_:didDetect:)メソッドの引数から、検出したタグのリストがNFCTagの配列として得られます。

これをNFCTagReaderSessionconnect(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]

脚注
  1. Core NFC Enhancements ↩︎

  2. このFeliCaシステムコードはオフィシャルに公開されているわけではないのですが、多くの人が調べているようで、Web検索すると簡単にわかります... ↩︎

  3. begin()メソッドは、NFCTagReaderSessionクラスの親クラスであるNFCReaderSessionクラスが準拠しているNFCReaderSessionProtocolプロトコルで定義されています。 ↩︎

  4. 非公開情報ですが、実際のところ、Web検索すると有志によってまとめられたかなり詳細な情報がすぐに見つかります。 ↩︎

Discussion