RxSwiftでCoreNFCを扱う

公開:2020/11/05
更新:2020/11/05
3 min読了の目安(約3300字TECH技術記事

CoreNFCのコールバックが辛い

NFCを読み取っていろいろやりたい時にコールバック地獄が辛い
ネストが深くなってめちゃくちゃ見にくくなる

func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
    let tag = tags.first!
    session.connect(
        to: tag
    ) { (error) in
        if let _ = error {
            session.invalidate(errorMessage: "NFCタグの読み取りに失敗しました。")
            return
        }
        
        guard case .feliCa(let feliCaTag) = tag else {
            session.restartPolling()
            return
        }
        
        feliCaTag.requestService(
            nodeCodeList: self.SERVICE_CODE_LIST
        ) { nodeKeyVersionList, error in
            if let _ = error {
                session.invalidate(errorMessage: "NFCタグの読み取りに失敗しました。")
                return
            }
            
            feliCaTag.readWithoutEncryption(
                serviceCodeList: self.SERVICE_CODE_LIST,
                blockList: self.BLOCK_LIST
            ) { status1, status2, dataList, error in
                session.invalidate()
                // do something.
            }
        }
    }
}

地獄から逃れる為のライブラリ作りました。

読み取り、コマンド実行に対応しているタグは下記の3種です
・FeliCa, MiFare, ISO7816
NFCISO15693及びNFCNDEFReaderSession/NFCVASReaderSessionは未実装のため使用できせん

RxCoreNFCでコールバック地獄を解決

NFCTagReaderSessionをラップしたクラスを実装し、Reactiveに扱えるように
Reactiveにする事で多段ネストの改善、タグのフィルターを完結に行えるように

let sessions = NFCTagReaderSession.rx.open(pollingOption: [.iso18092])
let tags = sessions.begin().tags().felicaTags()
let connectedTags = Observable.combineLatest(tags, sessions)
    .flatMap { tag, session in session.connect(tag) }
    .share()

let invalidates = connectedTags
    .requestService(nodeCodeList: SERVICE_CODE_LIST)
    .withLatestFrom(connectedTags)
    .readWithoutEncryption(serviceCodeList: SERVICE_CODE_LIST, blockList: BLOCK_LIST })
    .do(onNext: { result in
        // Do something
    })
    .withLatestFrom(sessions)
    .invalidate()
    .first()

button.rx.tap
    .flatMapFirst { invalidates }
    .subscribe()
    .disposed(by: disposeBag)

RxCoreNFCの扱い方

セッション生成

let sessions = NFCTagReaderSession.rx.open(pollingOption: [.iso18092])

タグ読み取りの開始

let sessions = NFCTagReaderSession.rx.open(pollingOption: [.iso18092])
let invalidates = sessions.invalidate()

button.rx.tap
    .flatMapFirst { invalidates }
    .subscribe()
    .disposed(by: disposeBag)

読み取ったタグの取得

let sessions = NFCTagReaderSession.rx.open(pollingOption: [.iso18092])
let invalidates = sessions
    .begin()
    .tags()
    .do(onNext: { tag in
        print(tag.isAvailable)
    })
    .withLatestFrom(sessions)
    .invalidate()

button.rx.tap
    .flatMapFirst { invalidates }
    .subscribe()
    .disposed(by: disposeBag)

タグを種別毎にフィルタリング

let sessions = NFCTagReaderSession.rx.open(pollingOption: [.iso18092])
let tags = sessions
    .begin()
    .tags()

let feliCaTags = tags
    .feliCaTags()
    .do(onNext: { tag in
        print(tag.currentIDm)
    })
    .map { _ in }
let miFareTags = tags
    .miFareTags()
    .do(onNext: { tag in
        print(tag.identifier)
    })
    .map { _ in }

let invalidates = feliCaTags.amb(miFareTags)
    .withLatestFrom(sessions)
    .invalidate()

button.rx.tap
    .flatMapFirst { invalidates }
    .subscribe()
    .disposed(by: disposeBag)