在iOS应用中实现近场通信标签读取,Core NFC是唯一受官方支持的框架。它并不直接暴露所有类型的NFC协议,而是将能力收拢在几个会话类之中,其中针对常见贴纸、海报上广泛使用的NDEF格式,系统提供了专门的读取路径。理解这套机制,第一步是弄清硬件与系统版本边界:iPhone 7及后续机型具备读取NDEF标签的射频能力,但系统权限、后台限制以及会话生命周期都由Core NFC统一管理。开发者若想让用户扫一下标签就拿到里面的网址或文本,必须主动创建会话对象并遵守代理协议,而不是依赖系统全局广播。

会话配置与权限声明
要让应用具备读取能力,首先必须在工程配置中打开近场通信功能开关,并在Info.plist里添加NFCReaderUsageDescription字段。这个字段是向用户申请权限时弹窗展示的说明文字,如果缺失,调用会话启动方法会直接抛出异常。很多初次接入的开发者在模拟器上跑通了编译,一到真机就崩溃,原因往往就是Plist里忘了写这个描述,或者写成了其他相似键名。系统对隐私管控很严格,NFC属于敏感硬件,用户首次触发读取动作时才会看到授权提示,且拒绝后只能去系统设置里手动开启。
代码层面,核心类是NFCTagReaderSession。它要求传入一个遵循NFCTagReaderSessionDelegate的代理对象,并指定轮询的标签类型。对于NDEF读取场景,应当使用pollingOption为.iso14443或让其自动适配,因为大多数NDEF标签基于ISO 14443 Type A。启动会话后,屏幕常亮、射频场开启,用户把手机背部贴近标签即可触发回调。下面的示例展示了最小可用配置:
import CoreNFC
class NFCReader: NSObject, NFCTagReaderSessionDelegate {
var session: NFCTagReaderSession?
func beginScan() {
guard NFCTagReaderSession.readingAvailable else {
print("当前设备不支持NFC读取")
return
}
session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self, queue: nil)
session?.alertMessage = "请将手机靠近NDEF标签"
session?.begin()
}
func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
// 会话已激活,射频场开启
}
func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
// 会话结束或出错
}
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
// 见后续解析小节
}
}
上述代码中,readingAvailable用于判断设备能力,避免在iPad或老机型上盲目启动。代理方法didDetect在标签进入场时被调用,此时还不能立即读取,必须先建立连接再查询NDEF消息。会话一旦失效便不能复用,每次扫描都要新建对象,这是框架设计的硬性约束,也防止了后台长时间占用射频造成耗电。
NDEF消息结构与记录提取
当didDetect回传NFCTag数组时,通常只包含一个标签。开发者需判断其具体类型,若是.miFare或.iso7816等且支持NDEF,则将其转换为NFCNDEFTag协议对象。随后调用readNDEF()方法异步获取NFCNDEFMessage。消息内部由records数组构成,每一个NFCNDEFRRecord都携带typeNameFormat、type、identifier与payload。TNF字段决定了如何解释载荷,例如.wellKnown代表遵循NFC论坛定义的标准记录,.absoluteURI则是完整URI字符串。
实际解析时,常见误区是把payload直接当成UTF8字符串打印。以文本记录为例,首字节高三位表示编码与状态,低五位表示语言码长度,后面紧跟如en这样的语言标识,真正的文本在偏移之后。如果忽略这一层,解析出的字符串开头会带有乱码字符。URI记录则更为复杂,首字节可能是缩写表索引,比如0x01代表https://www.,后续字节才是域名剩余部分。下面代码演示了安全提取文本与URI的过程:
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
guard let firstTag = tags.first else { return }
let ndefTag = firstTag.asNDEF ?? return
session.connect(to: firstTag) { error in
if error != nil { session.invalidate(errorMessage: "连接失败"); return }
ndefTag.readNDEF { message, readError in
if let msg = message {
for record in msg.records {
self.handleRecord(record)
}
session.invalidate()
} else {
session.invalidate(errorMessage: "读取失败")
}
}
}
}
func handleRecord(_ record: NFCNDEFRRecord) {
switch record.typeNameFormat {
case .wellKnown:
if let typeStr = String(data: record.type, encoding: .utf8), typeStr == "T" {
var payload = record.payload
let langLen = Int(payload[0] & 0x3F)
payload.removeFirst(langLen + 1)
if let text = String(data: payload, encoding: .utf8) {
print("文本记录: (text)")
}
}
case .absoluteURI:
if let uri = String(data: record.payload, encoding: .utf8) {
print("URI记录: (uri)")
}
default:
break
}
}
这段逻辑先剥离文本记录的语言前缀,再按UTF8解码,避免前面提到的乱码问题。对于绝对URI,直接转字符串即可,但若遇到.wellKnown下的URI类型U,则需要查表补全协议头。项目中若涉及多种标签来源,建议把解析层独立成工具类,这样业务层只关心最终得到的字符串或字典,不必重复处理字节细节。
错误处理与用户体验优化
Core NFC的会话是独占且易失效的。除了用户主动取消,标签移开、多次读取失败、甚至其他近场应用抢占都会触发didInvalidateWithError。错误码分布在NFCReaderError枚举中,比如.readerSessionInvalidationErrorUserCanceled表示用户点了取消,而.readerSessionInvalidationErrorSystemIsBusy说明系统射频正忙。精细区分这些错误,才能在界面上给出准确提示,而不是笼统地说“读取出错”。
另外,alertMessage属性是系统显示在扫码界面上的引导文案,它支持动态修改。当检测到特定类型标签后,可以改成“读取成功,正在处理”来降低用户焦虑。需要注意的是,会话从激活到失效最长约60秒,超时自动关闭,因此若业务需要连续扫多个标签,必须在每次失效后由用户再次触发 begin。下面示例展示了如何优雅地重启并分类错误:
func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
let nsError = error as NSError
if nsError.code == NFCReaderError.readerSessionInvalidationErrorUserCanceled.rawValue {
print("用户取消")
} else if nsError.code == NFCReaderError.readerSessionInvalidationErrorSystemIsBusy.rawValue {
print("系统忙,稍后重试")
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.beginScan()
}
} else {
print("其他错误: (error.localizedDescription)")
}
}
把重启逻辑放在主线程延时执行,可以避免立即轮询造成的资源争用。同时,在真实产品里应当把NFC能力与二维码、手动输入做成互补方案,毕竟不是所有iPhone型号都支持,且标签污损时射频识别率会下降。通过代理方法细致地捕捉每一种异常,才能让普通用户在地铁闸机、展览海报这类场景下顺畅地完成一次轻量级交互。
Core_NFCNDEFNFCTagReaderSession修改时间:2026-08-17 09:24:38