一、NFC的使用范围
苹果在iOS11上推出了NFC的功能,开发者可以根据自身的需要使用这个功能进行开发。NFC有读和写的功能。iOS11上的手机只支持读的功能,即可以通过手机的NFC功能读标签。而写的功能要iOS13以上的系统才支持。
二、NFC读标签
在读标签的功能上,主要用到的是NFCNDEFReaderSession这个类,它的初始化很简单,就是设置代理和队列(这里没有用到队列给nil)以及读成功后是否结束。然后调用beginSession方法,NFC就会开始读数据了。(这时候会弹出系统的)
弹窗。
self.session = [[NFCNDEFReaderSession alloc] initWithDelegate:self queue:nil invalidateAfterFirstRead:NO];
[self.session beginSession];
NFCNDEFReaderSession这个类要遵守2个协议NFCReaderSessionDelegate、NFCNDEFReaderSessionDelegate。它对于的协议方法有:
//开始链接上nfc的回调
- (void)readerSessionDidBecomeActive:(NFCReaderSession *)session
//链接报错了的回调
- (void)readerSession:(NFCReaderSession *)session didInvalidateWithError:(NSError *)error
//读取到NFC数据的回调(ios13以上支持)
- (void)readerSession:(NFCReaderSession *)session didDetectTags:(NSArray<__kindof id> *)tags
//读取到NFC数据的回调(ios11以上支持)
- (void)readerSession:(NFCNDEFReaderSession *)session didDetectNDEFs:(NSArray *)messages
// 这里重点是读取到信息后的回调方法,主要是对返回数据的解析
- (void)readerSession:(NFCNDEFReaderSession *)session didDetectNDEFs:(NSArray *)messages{
for (NFCNDEFMessage *message in messages) {
for (NFCNDEFPayload *record in message.records) {
NSString *dataStr = [[NSString alloc] initWithData:record.payload encoding:NSUTF8StringEncoding];
NSLog(@"扫描结果:%@",dataStr);
}
}
}
// 另一个读取到数据的代理方法,主要是对tag的解析。
id tag=tags.firstObject;
[session connectToTag:tag completionHandler:^(NSError * _Nullable error) {
if (error) {
session.alertMessage = @"连接NFC标签失败";
[self invalidateSession];
return;
}
[tag queryNDEFStatusWithCompletionHandler:^(NFCNDEFStatus status, NSUInteger capacity, NSError * _Nullable error) {
if (error) {
session.alertMessage = @"查询NFC标签状态失败";
[self invalidateSession];
return;
}
if (status == NFCNDEFStatusNotSupported) {
session.alertMessage = @"标签不是NDEF格式";
[self invalidateSession];
return;
}
[tag readNDEFWithCompletionHandler:^(NFCNDEFMessage * _Nullable message, NSError * _Nullable error) {
if (error) {
session.alertMessage = @"读取NFC标签失败";
[self invalidateSession];
}
else if (message==nil) {
session.alertMessage = @"NFC标签为空";
[self invalidateSession];
return;
}
else {
session.alertMessage = @"读取成功";
NSString* nfc_tag_content = @"";
for (NFCNDEFPayload *record in message.records) {
NSString *dataStr = [[NSString alloc] initWithData:record.payload encoding:NSUTF8StringEncoding];
NSLog(@"扫描结果:%@",dataStr);
}
[self invalidateSession];
}
}];
}];
}];
话不多说,直接上完整代码:
@interface WLNFCTagManager()
@property(nonatomic,strong)NFCNDEFReaderSession *session;//NFC会话对象
// 当前是否在读; YES为读操作。NO为写操作;
@property (nonatomic,assign) BOOL isReadAction;
@property (nonatomic,strong) NFCNDEFMessage *message;
@end
@implementation WLNFCTagManager
static WLNFCTagManager *sharedInstance;
+ (instancetype)sharedInstance
{
static dispatch_once_t onec;
dispatch_once(&onec, ^{
sharedInstance = [[WLNFCTagManager alloc] init];
});
return sharedInstance;
}
+ (NFCSupportsStatus)isSupportsNFCReading{
if (@available(iOS 11.0,*)) {
if (NFCNDEFReaderSession.readingAvailable == YES) {
return NFCSupportStatusYes;
}
else{
NSLog(@"%@",@"该机型不支持NFC功能!");
return NFCSupportStatusDeviceNo;
}
}
else {
NSLog(@"%@",@"当前系统不支持NFC功能!");
return NFCSupportStatusnSystemNo;
}
}
+ (NFCSupportsStatus)isSupportsNFCWrite{
if (@available(iOS 13.0,*)) {
if (NFCNDEFReaderSession.readingAvailable == YES) {
return NFCSupportStatusYes;
}
else{
NSLog(@"%@",@"该机型不支持NFC功能!");
return NFCSupportStatusDeviceNo;
}
}
else {
NSLog(@"%@",@"当前系统不支持NFC功能!");
return NFCSupportStatusnSystemNo;
}
}
- (void)startScan {
//最低硬件支持iphone7或7plus,系统最低支持为iOS11;
if ([WLNFCTagManager isSupportsNFCReading] == NFCSupportStatusYes) {
if (NFCNDEFReaderSession.readingAvailable){
self.session = [[NFCNDEFReaderSession alloc] initWithDelegate:self queue:nil invalidateAfterFirstRead:NO];
[self.session setAlertMessage:@"开始读卡啦"];
[self.session beginSession];
}
}
else
{
NSLog(@"不支持")
}
}
- (void)stopScan {
[self invalidateSession];
self.session = nil;
//关闭NFC
}
- (void)writeMessage:(NSString *)message API_AVAILABLE(ios(11.0)){
if([WLNFCTagManager isSupportsNFCWrite] == NFCSupportStatusYes){
self.isReadAction = NO;
// 把string 封装成写入的对象;
//self.message = message;
[self startScan];
}
}
- (void)readMessage {
self.isReadAction = YES;
[self startScan];
}
- (void)invalidateSession{
if(@available(iOS 11.0,*)){
[self.session invalidateSession];
}
}
// 读取成功回调。ios11~12回调这个方法
- (void)readerSession:(NFCNDEFReaderSession *)session didDetectNDEFs:(NSArray *)messages API_AVAILABLE(ios(11.0)) API_AVAILABLE(ios(11.0)){
NSString *nfc_tag_content = @"";
if (@available(iOS 11.0, *)){
if(NFCNDEFReaderSession.readingAvailable){
for (NFCNDEFMessage *message in messages) {
for (NFCNDEFPayload *record in message.records) {
NSString *dataStr = [[NSString alloc] initWithData:record.payload encoding:NSUTF8StringEncoding];
NSLog(@"扫描结果:%@",dataStr);
}
}
[session invalidateSession];
}
} else {
//设备不支持NFC
NSLog(@"不支持NFC");
// Fallback on earlier versions
}
}
// 读取成功回调。ios13回调这个方法
- (void)readerSession:(NFCNDEFReaderSession *)session didDetectTags:(NSArray<__kindof id> *)tags API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(watchos, macos, tvos){
WLWeakSelf(self);
dispatch_async(dispatch_get_main_queue(), ^{
WLStrongSelf(self);
if (tags.count>1) {
session.alertMessage=@"存在多个标签";
[session restartPolling];
return;
}
id tag=tags.firstObject;
[session connectToTag:tag completionHandler:^(NSError * _Nullable error) {
if (error) {
session.alertMessage = @"连接NFC标签失败";
[self invalidateSession];
return;
}
[tag queryNDEFStatusWithCompletionHandler:^(NFCNDEFStatus status, NSUInteger capacity, NSError * _Nullable error) {
if (error) {
session.alertMessage = @"查询NFC标签状态失败";
[self invalidateSession];
return;
}
if (status == NFCNDEFStatusNotSupported) {
session.alertMessage = @"标签不是NDEF格式";
[self invalidateSession];
return;
}
if (strongself.isReadAction) {
//读
[tag readNDEFWithCompletionHandler:^(NFCNDEFMessage * _Nullable message, NSError * _Nullable error) {
if (error) {
session.alertMessage = @"读取NFC标签失败";
[self invalidateSession];
}
else if (message==nil) {
session.alertMessage = @"NFC标签为空";
[self invalidateSession];
return;
}
else {
session.alertMessage = @"读取成功";
NSString* nfc_tag_content = @"";
for (NFCNDEFPayload *record in message.records) {
NSString *dataStr = [[NSString alloc] initWithData:record.payload encoding:NSUTF8StringEncoding];
NSLog(@"扫描结果:%@",dataStr);
}
[self invalidateSession];
}
}];
}
else{
//写数据
[tag writeNDEF:self.message completionHandler:^(NSError * _Nullable error) {
if (error) {
session.alertMessage = @"写入失败";
}
else {
session.alertMessage = @"写入成功";
}
[self invalidateSession];
}];
}
}];
}];
});
}
//错误回调
- (void)readerSession:(NFCReaderSession *)session didInvalidateWithError:(NSError *)error API_AVAILABLE(ios(11.0)){
NSLog(@"错误回调:%@",error.userInfo[@"NSLocalizedDescription"]);
if(error.code != 200)
{
[self stopScan];
}
}
//挂起变成活跃
- (void)readerSessionDidBecomeActive:(nonnull NFCReaderSession *)session API_AVAILABLE(ios(11.0)){
}
@end
三、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卡的基本应用作出了具体规定。
4、NFCISO7816Tag 的开发
第一:需要在xcode的info.plist里设置NFCISO7816的许可
第二: 需要一部iOS3系统以上的手机。苹果在iOS13上才支持7816tag的写操作。
第三:开始写代码,showCode
/*!
* @enum NFCPollingOption
*
* @constant NFCPollingISO14443 Support both Type A & B modulation. NFCTagTypeISO7816Compatible and NFCTagTypeMiFare tags will be discovered.
* @constant NFCPollingISO15693 NFCTagTypeISO15693 tag will be discovered.
* @constant NFCPollingISO18092 NFCTagTypeFeliCa tag will be discovered.
*/
// 从苹果的注释可以看出要支持ISO7816需要选择的pollingOption是NFCPollingISO14443
tagSession = [[NFCTagReaderSession alloc]initWithPollingOption:NFCPollingISO14443 delegate:self queue:dispatch_queue_create("beckhams",DISPATCH_QUEUE_SERIAL)];
tagSession.alertMessage = message;
[tagSession beginSession];
// 遵守协议:NFCTagReaderSessionDelegate,实现协议对应的代理
// 读取失败的回调
- (void)tagReaderSession:(NFCTagReaderSession *)session didInvalidateWithError:(NSError *)error
// 开始链接上的回调
- (void)tagReaderSessionDidBecomeActive:(NFCTagReaderSession *)session
// 读取到tag的回调 (这里重点说明这个函数)
- (void)tagReaderSession:(NFCTagReaderSession *)session didDetectTags:(NSArray<__kindof id> *)tags {
if (tags.count > 1){
NSLog(@"读卡错误");
return;
}
Tag7816 = [tags.firstObject asNFCISO7816Tag];
//这里的Tag7816实例是用于后面发送指令的对象。
if (Tag7816 == nil){
NSLog(@"读取到的非7816卡片");
return;
}
// 这里获取到的AID就是第一步中在info.plist中设置的ID (A000000000)这个值一般是卡商提供的,代表卡的应用表示。
NSLog(@"%@",Tag7816.initialSelectedAID);
__weak typeof(self) weakSelf = self;
[tagSession connectToTag:Tag7816 completionHandler:^(NSError * _Nullable error) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (error){
NSLog(@"应用选择失败 ...");
return;
}
// 这里就可以开始执行指令和cpu卡交互了。
}];
}
/*!
* @method sendCommandAPDU:completionHandler:
*
* @param apdu The command APDU object
* @param completionHandler Completion handler called when the operation is completed. error is nil if operation succeeds.
* A @link NFCErrorDomain @link/ error is returned when there is a communication issue with the tag. responseData may be
* empty. Command processing status bytes (SW1-SW2) are always valid.
*
* @discussion Send a command APDU to the tag and receives a response APDU. Note that a SELECT command with a P1 value of 0x04 (seelction by DF name)
* will be checked against the values listed in the "com.apple.developer.nfc.readersession.iso7816.select-identifiers" in the Info.plist.
* Selecting an application outside of the permissible list will result in a NFCReaderErrorSecurityViolation error.
*/
// 这个函数用于给CPU卡片发送指令的函数。通过构建NFCISO7816APDU对象发送指令,并获得CPU卡片返回的值
- (void)sendCommandAPDU:(NFCISO7816APDU *)apdu
completionHandler:(void(^)(NSData *responseData, uint8_t sw1, uint8_t sw2, NSError * _Nullable error))completionHandler;
// 发送指令的示例代码:
-(void)sendApduSingle:(NSString *)apduStr{
// apduStr 是发送的指令字符串,比如 00A5030004B000000033434561
*error = nil;
//方式一
NSData *apduData = [GenRoutines StrToHex: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
[Tag7816 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);
lerror = error;
lsw = sw1;
lsw = (lsw << 8) | sw2;
if (responseData) {
recvData = [[NSData alloc]initWithData:responseData];
}
// 拿到返回的数据了,根据具体的业务需求去写代码。。。。
}];
}
附上苹果关于NFC开发文档链接 https://developer.apple.com/documentation/corenfc