NFC的使用

一、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<NFCTag>> *)tags
//读取到NFC数据的回调(ios11以上支持)
- (void)readerSession:(NFCNDEFReaderSession *)session didDetectNDEFs:(NSArray<NFCNDEFMessage *> *)messages
//  这里重点是读取到信息后的回调方法,主要是对返回数据的解析
- (void)readerSession:(NFCNDEFReaderSession *)session didDetectNDEFs:(NSArray<NFCNDEFMessage *> *)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()<NFCReaderSessionDelegate,NFCNDEFReaderSessionDelegate>

@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<NFCNDEFMessage *> *)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<NFCNDEFTag>> *)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的许可

image.png

第二: 需要一部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<NFCTag>> *)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

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

推荐阅读更多精彩内容

  • 一.环境 系统:iOS 11+;(现在还没推送,可以使用iTunes通过固件升级); 开发工具:Xcode9.0+...
    WHZ闹哪样阅读 3,727评论 2 7
  • iOS11开放了NFC的功能,但是仅限于iPhone7和7plus上使用,并且只开放了读取功能,所以这个版本的NF...
    傍观者阅读 7,396评论 1 2
  • 先了解几个概念什么是NFC?NDEF指的是什么?什么是CoreNFC? 什么是 NFC NFC(Near Fiel...
    A訫飛Flyme阅读 10,795评论 10 6
  • 目标:将门禁卡、考勤卡、会员卡、停车卡、电梯卡等等各种卡模拟进手机里,模拟后可用手机代替刷卡,无需root,不用电...
    啥都能写阅读 1,878评论 0 8
  • 原文链接: http://fighting300.com... 大家可能听过NFC这项功能,或者有可能你每天都在使...
    fighting300阅读 4,522评论 10 8