iOS NFC 功能开发

在项目开发中,使用到了NFC功能,然后对NFC功能进行了研究和使用总结。记录下来可以方便后续的使用。

NFC的官方文档:https://developer.apple.com/documentation/corenfc/nfctagreadersession?language=objc

1、NFC简介

NFC(Near Field Communication,NFC)近场通信,是一种短距高频的无线电技术,在13.56MHz频率运行于10厘米距离内。其传输速度有106 Kbit/秒、212 Kbit/秒或者424 Kbit/秒三种。

使用了NFC技术的设备(比如手机)可以在彼此靠近的情况下进行数据交换,是由非接触式射频识别(RFID)及互连互通技术整合演变而来,通过在单一芯片上集成感应式读卡器、感应式卡片和点对点通信的功能,利用移动终端实现移动支付、电子票务、门禁、移动身份识别、防伪等应用。目前,苹果的CoreNFC对NFC的格式支持有限,暂时仅支持NDEF格式。

2、NDEF 简介

NDEF(NFC Data Exchange Format)是一种能够在NFC设备或者标签之间进行信息交换的数据格式。NDEF格式由各种 NDEF Messages 和 NDEF Records 组成。NDEF格式使用了一种容易理解的格式来存储和交换信息,如:URI、纯文本等等。

NFC标签,像Mifare Classic卡片可以配置为NDEF标签,通过一个NFC设备写入的数据可以被其他NDEF兼容的设备访问。NDEF消息还可以用于两个活跃的NFC设备之间“点对点”模式交换数据。

一、NFC 启动前配置

1、修改开发者账号中 APP ID 模块权限,勾选NFC功能

APP ID
☑️ NFC 模块

2、修改项目中配置,添加 NFC 功能模块

添加 NFC 功能模块
添加 NFC 模块成功
配置文件生成 . entitlements 文件

3、配置文件配置NFC权限

添加 NFCReaderUsageDescription 权限描述
<key>NFCReaderUsageDescription</key>
<string>App Requires NFC to read the card details</string>

添加 iso7816 协议描述
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
<string>A000000243001300000001FF</string>
<string>A0000002430013000000010109</string>
<string>A00000024300130000000101</string>
<string>00000000000000</string>
</array>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>NDEF</string>
<string>TAG</string>
</array>

添加 NFC 权限描述
添加 iso7816 协议描述

二、NFC 实际应用使用

1、NFC 代码使用

导入头文件

#import <CoreNFC/CoreNFC.h>

@property (nonatomic, strong) NFCTagReaderSession *session;
@property ( strong , nonatomic ) id<NFCISO7816Tag> currentTag;

启用NFC功能代码,以下是使用 NFCTagReaderSession 进行的操作。

if (@available(iOS 13.0, *)) {
    // 初始化 NFC 设置代理 NFCTagReaderSessionDelegate
    self.session = [[NFCTagReaderSession alloc]
                initWithPollingOption:NFCPollingISO14443 delegate:self queue:nil];
    // NFC 显示提示信息
    self.session.alertMessage = @"准备扫描,请将卡片贴近手机";
    // 开启 NFC
    [self.session beginSession];

} else {
     [AlertView showAlertTitle:ALERT withMessage:NFC_NOT_SUPPORT onView:self];
}

以下是使用 NFCTagReaderSessionDelegate 进行的操作。

#pragma mark - NFCNDEFReaderSessionDelegate
//读取失败回调-读取成功后还是会回调这个方法
- (void)tagReaderSessionDidBecomeActive:(NFCTagReaderSession *)session  API_AVAILABLE(ios(13.0)){
    NSLog(@"tagReaderSessionDidBecomeActive");
}

- (void)tagReaderSession:(NFCTagReaderSession *)session didInvalidateWithError:(NSError *)error  API_AVAILABLE(ios(13.0)){
    NSLog(@"readerSession:didInvalidateWithError: (%@)", [error localizedDescription]);
}

- (void)tagReaderSession:(NFCTagReaderSession *)session didDetectTags:(NSArray<__kindof id<NFCTag>> *)tags   API_AVAILABLE(ios(13.0)){
     NSLog(@"readerSession:didDetectTags");
    if ([tags count] > 1) {
        [session setAlertMessage:@"More than 1 tag is detected, please try again"];
        [session restartPolling];
        return;
    }
    
     id<NFCTag> firstTag =  tags[0];
     NSLog(@"firstTag %@",firstTag);
    
    if (firstTag.type == NFCTagTypeMiFare) {
        [session setAlertMessage:@"A tag that is not iso7816 is detected, please try again with tag iso7816"];
        NSLog(@"session restartPolling");
        [session restartPolling];
    }
    
//    NSString *requestId = [Utils generateSecureKey];

    dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void) {
        
    if (@available(iOS 13.0, *)) {
        if (firstTag.type == NFCTagTypeISO7816Compatible) {
            id<NFCISO7816Tag> iso7816Tag = [firstTag asNFCISO7816Tag];
            @try {
                
                NSLog(@"Tag7816.initialSelectedAID:%@",iso7816Tag.initialSelectedAID);
                   __weak typeof(self) weakSelf = self;
                [self.session connectToTag:iso7816Tag completionHandler:^(NSError * _Nullable error) {
                       __strong typeof(weakSelf) strongSelf = weakSelf;
                       if (error){
                           NSLog(@"TconnectToTag:%@",error);
                           return;
                       }
                       self.currentTag = iso7816Tag;
                       self.session.alertMessage = @"已识别到NFC";
                       // 这里就可以开始执行指令和cpu卡交互了。
                       [self sendApduSingle:@"00A400000112501"];//16进制指令
                }];

            
//
                dispatch_async(dispatch_get_main_queue(), ^{
        
                    [session setAlertMessage:@"Reading Completed, session going to close"];
                    [session invalidateSession];
                });// update UI main queue
              }
            @catch (NSException *exception) {
                 [session invalidateSession];
                  NSString *exe =[NSString stringWithFormat:@"%@\n%@\n%@",exception.name,exception.description,exception.userInfo];
//                  [AlertView showAlertTitle:ALERT withMessage:exe onView:self];
                NSLog(@"exception==%@",exception);
              }// catch
              @finally {
              }// finally
        }
     } else {
         dispatch_async(dispatch_get_main_queue(), ^{
            // Fallback on earlier versions
             NSLog(@"Fallback on earlier versions");
//            [AlertView showAlertTitle:ALERT withMessage:NFC_NOT_SUPPORT onView:self];
         });// update UI main queue
      }
   });// background queue
}

二、NFC写数据的能力

NFC写数据的功能主要表现为可以写数据到对应的卡上(和具体硬件进行交互,比如CPU卡)注:本文主要是手机nfc与CPU卡就ISO7816协议间的交互。

1、什么是CPU卡

CPU 卡又叫智能卡,卡内具有中央处理器(CPU)、随机存储器(RAM)、程序存储器(ROM)、数据存储器(EEPROM)以及片内操作系统(COS)。CPU 卡可适用于金融、保险、*、政府行业等多个领域,具有用户空间大、读取速度快、支持一卡多用等特点,并已经通过中国人民银行和国家商秘委的认证。

2、CPU 卡的标准化

由于当前世界各国经济正在向国际化方向发展,全球化的金融服务系统纷纷建立起来,这就带来了一个卡的互操作性问题。同一张卡,在不同的国家、不同的环境下都要能够使用。要解决这个问题,只有制定一系列国际标准,使CPU卡及其接口设备制造商按照统一的标准,制造统一接口规格的产品,以保证不同国家、不同行业都采用统一的CPU卡软硬件技术规范开发应用系统,这样才能实现不同厂家生产的CPU卡之间的互换性和接口设备的共享。国际标准化组织从1987年开始,相继制定和颁布了CPU卡的国际标准。有关CPU卡本身的标准有:
ISO 10536:识别卡-非接触式的集成电路卡
ISO 7816:识别卡-带触点的集成电路卡
ISO7816-1:规定卡的物理特性。卡的物理特性中描述了卡应达到的防护紫外线的能力、X光照射的剂量、卡和触点的机械强度、抗电磁干扰能力等等。
ISO7816-2:规定卡的尺寸和位置。
ISO7816-3:规定卡的电信号和传输协议。传输协议包括两种:同步传输协议和异步传输协议
ISO7816-4:规定卡的行业间交换用命令。包括:在卡与读写间传送的命令和应答信息内容;在卡中的文件、数据结构及访问方法;定义在卡中的文件和数据访问权限及安全结构。

3、有关金融领域CPU卡应用的标准有:

ISO 9992:金融交易卡-集成电路卡与受卡接受设备之间的信息
ISO 14443:识别卡-非接触卡规范(距离10cm)
ISO 10202:金融交易卡-使用集成电路卡的金融交易系统的安全结构
EMV:支付系统的集成电路卡规范和支付系统的集成电路卡终端规范。中国金融集成电路(IC)卡规范:1998年3月中国人民银行等近十家金融单位在采用国际标准和国外先进技术的原则下,以ISO标准和Europay、Mastercard、Visa三大组织研制的EMV96为基础,结合国内CPU卡的应用实际需要,对我国金融CPU卡的基本应用作出了具体规定。

NFC 写入 CPU卡方法,使用的是NFCTagReaderSession

- (void)tagReaderSession:(NFCTagReaderSession *)session didDetectTags:(NSArray<__kindof id<NFCTag>> *)tags API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(watchos, macos, tvos){
    if (tags.count > 1){
        NSLog(@"读卡错误");
        return;
    }
    
    
    id<NFCISO7816Tag> Tag7816 = [tags.firstObject asNFCISO7816Tag];
    //这里的Tag7816实例是用于后面发送指令的对象。
    if (Tag7816 == nil){
        NSLog(@"读取到的非7816卡片");
        return;
    }
    // 这里获取到的AID就是第一步中在info.plist中设置的ID (A000000000)这个值一般是卡商提供的,代表卡的应用表示。
    NSLog(@"Tag7816.initialSelectedAID:%@",Tag7816.initialSelectedAID);
    __weak typeof(self) weakSelf = self;
    [self.tagReaderSession connectToTag:Tag7816 completionHandler:^(NSError * _Nullable error) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (error){
            NSLog(@"TconnectToTag:%@",error);
            return;
        }
        self.currentTag = Tag7816;
        self.tagReaderSession.alertMessage = @"已识别到NFC";
        // 这里就可以开始执行指令和cpu卡交互了。
        [self sendApduSingle:@"00A400000112501"];//16进制指令
    }];
}
// 发送指令的示例代码:
-(void)sendApduSingle:(NSString *)apduStr{
    //  apduStr 是发送的指令字符串,比如 00A5030004B000000033434561
    NSData *apduData = [self convertHexStrToData:apduStr]; // 把指令转成data格式
    NFCISO7816APDU *cmd = [[NFCISO7816APDU alloc]initWithData:apduData];  // 初始化 NFCISO7816APDU。
    
    __block NSData *recvData = nil;
    __block NSError *lerror = nil;
    __block BOOL bRecv = NO;
    __block int lsw = 0;
    NSLog(@"send data => %@", apduData);
    
    // 这里的Tag7816就是上面协议中拿到的tag
    [self.currentTag sendCommandAPDU:cmd completionHandler:^(NSData * _Nonnull responseData, uint8_t sw1, uint8_t sw2, NSError * _Nullable error) {
        NSLog(@"------resp:%@ sw:%02x%02x  error:%@", responseData, sw1, sw2, error);
        NSLog(@"responseData十六进制:%@", [self convertApduListDataToHexStr:responseData]);
        lerror = error;
        lsw = sw1;
        lsw = (lsw << 8) | sw2;
        if (responseData) {
            recvData = [[NSData alloc]initWithData:responseData];
        }
        // 拿到返回的数据了,根据具体的业务需求去写代码。。。。
        [self invalidateSession];
    }];
}


//将字符串转NSData
- (NSData *)convertHexStrToData:(NSString *)str {
    if (!str || [str length] == 0) {
        return nil;
    }
    
    NSMutableData *hexData = [[NSMutableData alloc] initWithCapacity:8];
    NSRange range;
    if ([str length] % 2 == 0) {
        range = NSMakeRange(0, 2);
    } else {
        range = NSMakeRange(0, 1);
    }
    for (NSInteger i = range.location; i < [str length]; i += 2) {
        unsigned int anInt;
        NSString *hexCharStr = [str substringWithRange:range];
        NSScanner *scanner = [[NSScanner alloc] initWithString:hexCharStr];
        
        [scanner scanHexInt:&anInt];
        NSData *entity = [[NSData alloc] initWithBytes:&anInt length:1];
        [hexData appendData:entity];
        
        range.location += range.length;
        range.length = 2;
    }
    return hexData;
}


//NSData转字符串
-(NSString *)convertApduListDataToHexStr:(NSData *)data{
        if (!data || [data length] == 0) {
            return @"";
        }
        NSMutableString *string = [[NSMutableString alloc] initWithCapacity:[data length]];
        [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
            unsigned char *dataBytes = (unsigned char*)bytes;
            for (NSInteger i = 0; i < byteRange.length; i++) {
                NSString *hexStr = [NSString stringWithFormat:@"%x", (dataBytes[i]) & 0xff];
                if ([hexStr length] == 2) {
                        [string appendString:hexStr];
                } else {
                    [string appendFormat:@"0%@", hexStr];
                }
            }
        }];
        return [string uppercaseString];
}

- (void)tagReaderSession:(NFCTagReaderSession *)session didInvalidateWithError:(NSError *)error API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(watchos, macos, tvos){
    NSLog(@"%@",error);
}

需要注意点:使用NFC也需要打开蓝牙权限

如果没有蓝牙权限,可能会崩溃,需求申请蓝牙权限。

    <key>NSBluetoothAlwaysUsageDescription</key>
    <string>Bluetooth open</string>
    <key>NSBluetoothPeripheralUsageDescription</key>
    <string>App need your approval to access to the bluetooth</string>

注意点:

NDEF 的 URI 格式 限制
这些情况下无法使用后台 NFC 功能:

重启手机后,没有解锁过
有一个 NFC 进程正在运行
Apple 钱包正在使用
相机正在使用
飞行模式下

具体 iOS NFC 开发流程

AppleID 开通 NFC Tag Reading 功能。
工程进行描述文案等配置。
代码开发。
如果需要支持后台扫描,还需要支持 Universal Link 功能。
Universal Link 功能使用参考文档

NFC和CPU卡交互我的项目中并没有使用到,全部内容如有问题,欢迎大佬及时纠正和补充。

参考文献:

NFC 苹果官网
NFC 与 CPU卡交互
NFCNDEFReaderSession 参考文档
iOS开发之NFC的使用
iOS NFC

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 一、NFC的使用范围 苹果在iOS11上推出了NFC的功能,开发者可以根据自身的需要使用这个功能进行开发。NFC有...
    nick5683阅读 2,463评论 2 3
  • 一、NFC的使用范围 苹果在iOS11上推出了NFC的功能,开发者可以根据自身的需要使用这个功能进行开发。NFC有...
    三国韩信阅读 4,026评论 3 23
  • 一、了解NFCiOS开发关于NFC的使用,其实官方文档已经很详细了,先上官方文档:文档地址:https://dev...
    Streamsle阅读 14,756评论 23 35
  • 昨天公司突然有个要用到NFC 功能的项目,我一想前几年挺火的,网上肯定也有不少例子,觉得分分钟搞定的,然而…… 虽...
    FlyFighting阅读 36,447评论 6 58
  • 0x00 概述 许多提供NFC功能的基于Android的设备已经支持NFC卡模拟。在大多数情况下,该卡由设备中的单...
    Killshadow阅读 12,387评论 2 8