在项目开发中,使用到了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功能
2、修改项目中配置,添加 NFC 功能模块
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 实际应用使用
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