在項目開發(fā)中,使用到了NFC功能被碗,然后對NFC功能進行了研究和使用總結(jié)。記錄下來可以方便后續(xù)的使用。
NFC的官方文檔:https://developer.apple.com/documentation/corenfc/nfctagreadersession?language=objc
1木柬、NFC簡介
NFC(Near Field Communication,NFC)近場通信,是一種短距高頻的無線電技術淹办,在13.56MHz頻率運行于10厘米距離內(nèi)眉枕。其傳輸速度有106 Kbit/秒、212 Kbit/秒或者424 Kbit/秒三種怜森。
使用了NFC技術的設備(比如手機)可以在彼此靠近的情況下進行數(shù)據(jù)交換速挑,是由非接觸式射頻識別(RFID)及互連互通技術整合演變而來,通過在單一芯片上集成感應式讀卡器副硅、感應式卡片和點對點通信的功能姥宝,利用移動終端實現(xiàn)移動支付、電子票務恐疲、門禁腊满、移動身份識別套么、防偽等應用。目前碳蛋,蘋果的CoreNFC對NFC的格式支持有限胚泌,暫時僅支持
NDEF
格式。
2疮蹦、NDEF
簡介
NDEF(NFC Data Exchange Format)是一種能夠在NFC設備或者標簽之間進行信息交換的數(shù)據(jù)格式诸迟。NDEF格式由各種 NDEF Messages 和 NDEF Records 組成。NDEF格式使用了一種容易理解的格式來存儲和交換信息愕乎,如:URI阵苇、純文本等等。
NFC標簽感论,像Mifare Classic卡片可以配置為NDEF標簽,通過一個NFC設備寫入的數(shù)據(jù)可以被其他NDEF兼容的設備訪問绅项。NDEF消息還可以用于兩個活躍的NFC設備之間“點對點”模式交換數(shù)據(jù)。
一比肄、NFC 啟動前配置
1快耿、修改開發(fā)者賬號中 APP ID 模塊權限,勾選NFC功能
2芳绩、修改項目中配置掀亥,添加 NFC 功能模塊
3、配置文件配置NFC權限
添加 NFCReaderUsageDescription 權限描述
<key>NFCReaderUsageDescription</key>
<string>App Requires NFC to read the card details</string>
添加 iso7816 協(xié)議描述
<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
//讀取失敗回調(diào)-讀取成功后還是會回調(diào)這個方法
- (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";
// 這里就可以開始執(zhí)行指令和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寫數(shù)據(jù)的能力
NFC寫數(shù)據(jù)的功能主要表現(xiàn)為可以寫數(shù)據(jù)到對應的卡上(和具體硬件進行交互,比如CPU卡)注:本文主要是手機nfc與CPU卡就ISO7816協(xié)議間的交互许师。
1房蝉、什么是CPU卡
CPU 卡又叫智能卡,卡內(nèi)具有中央處理器(CPU)微渠、隨機存儲器(RAM)惨驶、程序存儲器(ROM)、數(shù)據(jù)存儲器(EEPROM)以及片內(nèi)操作系統(tǒng)(COS)敛助。CPU 卡可適用于金融粗卜、保險、*纳击、政府行業(yè)等多個領域续扔,具有用戶空間大攻臀、讀取速度快、支持一卡多用等特點纱昧,并已經(jīng)通過中國人民銀行和國家商秘委的認證刨啸。
2、CPU 卡的標準化
由于當前世界各國經(jīng)濟正在向國際化方向發(fā)展识脆,全球化的金融服務系統(tǒng)紛紛建立起來设联,這就帶來了一個卡的互操作性問題。同一張卡灼捂,在不同的國家离例、不同的環(huán)境下都要能夠使用。要解決這個問題悉稠,只有制定一系列國際標準宫蛆,使CPU卡及其接口設備制造商按照統(tǒng)一的標準,制造統(tǒng)一接口規(guī)格的產(chǎn)品的猛,以保證不同國家耀盗、不同行業(yè)都采用統(tǒng)一的CPU卡軟硬件技術規(guī)范開發(fā)應用系統(tǒng),這樣才能實現(xiàn)不同廠家生產(chǎn)的CPU卡之間的互換性和接口設備的共享卦尊。國際標準化組織從1987年開始叛拷,相繼制定和頒布了CPU卡的國際標準碗誉。有關CPU卡本身的標準有:
ISO 10536:識別卡-非接觸式的集成電路卡
ISO 7816:識別卡-帶觸點的集成電路卡
ISO7816-1:規(guī)定卡的物理特性匣沼。卡的物理特性中描述了卡應達到的防護紫外線的能力赚楚、X光照射的劑量淌友、卡和觸點的機械強度、抗電磁干擾能力等等骇陈。
ISO7816-2:規(guī)定卡的尺寸和位置震庭。
ISO7816-3:規(guī)定卡的電信號和傳輸協(xié)議。傳輸協(xié)議包括兩種:同步傳輸協(xié)議和異步傳輸協(xié)議
ISO7816-4:規(guī)定卡的行業(yè)間交換用命令你雌。包括:在卡與讀寫間傳送的命令和應答信息內(nèi)容器联;在卡中的文件、數(shù)據(jù)結(jié)構(gòu)及訪問方法婿崭;定義在卡中的文件和數(shù)據(jù)訪問權限及安全結(jié)構(gòu)拨拓。
3、有關金融領域CPU卡應用的標準有:
ISO 9992:金融交易卡-集成電路卡與受卡接受設備之間的信息
ISO 14443:識別卡-非接觸卡規(guī)范(距離10cm)
ISO 10202:金融交易卡-使用集成電路卡的金融交易系統(tǒng)的安全結(jié)構(gòu)
EMV:支付系統(tǒng)的集成電路卡規(guī)范和支付系統(tǒng)的集成電路卡終端規(guī)范氓栈。中國金融集成電路(IC)卡規(guī)范:1998年3月中國人民銀行等近十家金融單位在采用國際標準和國外先進技術的原則下渣磷,以ISO標準和Europay、Mastercard授瘦、Visa三大組織研制的EMV96為基礎醋界,結(jié)合國內(nèi)CPU卡的應用實際需要竟宋,對我國金融CPU卡的基本應用作出了具體規(guī)定。
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實例是用于后面發(fā)送指令的對象形纺。
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";
// 這里就可以開始執(zhí)行指令和cpu卡交互了逐样。
[self sendApduSingle:@"00A400000112501"];//16進制指令
}];
}
// 發(fā)送指令的示例代碼:
-(void)sendApduSingle:(NSString *)apduStr{
// apduStr 是發(fā)送的指令字符串蜗字,比如 00A5030004B000000033434561
NSData *apduData = [self convertHexStrToData:apduStr]; // 把指令轉(zhuǎn)成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就是上面協(xié)議中拿到的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];
}
// 拿到返回的數(shù)據(jù)了脂新,根據(jù)具體的業(yè)務需求去寫代碼挪捕。。戏羽。担神。
[self invalidateSession];
}];
}
//將字符串轉(zhuǎn)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轉(zhuǎn)字符串
-(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 開發(fā)流程
AppleID 開通 NFC Tag Reading 功能酷宵。
工程進行描述文案等配置亥贸。
代碼開發(fā)。
如果需要支持后臺掃描浇垦,還需要支持Universal Link
功能炕置。
Universal Link 功能使用參考文檔
NFC和CPU卡交互我的項目中并沒有使用到,全部內(nèi)容如有問題男韧,歡迎大佬及時糾正和補充朴摊。
參考文獻:
NFC 蘋果官網(wǎng)
NFC 與 CPU卡交互
NFCNDEFReaderSession 參考文檔
iOS開發(fā)之NFC的使用
iOS NFC