Swift元组类型别名实例化无法转换为同类型声明变量的原因
- 运行环境:macOS Monterey 12.4
- 开发工具:Xcode 13.4
- 开发语言:Swift
- 核心诉求:明确错误根本触发原因,不需要绕过错误的解决方案
- 触发位置:
CKModifyRecordsOperation.perRecordSaveBlock回调内,对saveResult做switch匹配进入.success(CKRecord)分支,使用自定义typealias实例化对应元组时触发编译错误
CloudKit Contact 记录类型定义
RECORD TYPE Contact ( "___createTime" TIMESTAMP QUERYABLE SORTABLE, "___createdBy" REFERENCE QUERYABLE, "___etag" STRING QUERYABLE SEARCHABLE SORTABLE, "___modTime" TIMESTAMP QUERYABLE SORTABLE, "___modifiedBy" REFERENCE QUERYABLE, "___recordID" REFERENCE QUERYABLE, debugTag STRING QUERYABLE SEARCHABLE SORTABLE, ownerContactIdentifier STRING QUERYABLE SEARCHABLE SORTABLE, referenceToGroupRecord REFERENCE QUERYABLE, vCardContactData BYTES, GRANT WRITE TO "_creator", GRANT CREATE TO "_icloud", GRANT READ TO "_world" );
自定义元组类型别名
typealias TupleContactRecord = (contactRecord: CKRecord, ownerContactIdentifier: String?, localContactIdentifier: String?, contact: CNContact?, vCardUnarchiveError: Error?, contactSaveError: Error?, setLocalContainerIdentifierFieldOfContactRecordError: Error?, saveRecordError: Error?)
编译错误提示
Tuple type '(contactRecord: (CKRecord), ownerContactIdentifier: String?, localContactIdentifier: String?, contact: __CKRecordObjCValue?, vCardUnarchiveError: _const Error?, contactSaveError: _const Error?, setLocalContainerIdentifierFieldOfContactRecordError: _const Error?, saveRecordError: _const Error?)' is not convertible to tuple type 'TupleContactRecord' (aka '(contactRecord: CKRecord, ownerContactIdentifier: Optional, localContactIdentifier: Optional, contact: Optional, vCardUnarchiveError: Optional, contactSaveError: Optional, setLocalContainerIdentifierFieldOfContactRecordError: Optional, saveRecordError: Optional)')
观察到的错误特征:待赋值元组和声明的目标元组类型唯一可见差异,是待赋值元组第一个参数外额外包裹了括号,目标元组对应首个参数无额外括号。
错误复现核心代码
错误触发位置:红色下划线位于TupleContactRecord的首字母大写T下方
if #available(iOS 15.0, *) { modifyRecordsOperation.perRecordSaveBlock = { recordID, saveResult in var newTuple: TupleContactRecord? = nil switch saveResult { case .failure(let error): break case .success(let savedContactRecord): newTuple = TupleContactRecord(contactRecord: savedContactRecord, // 错误提示出现在此处 ownerContactIdentifier: savedContactRecord[DatabaseNameStrings.fieldOwnerContactIdentifier], localContactIdentifier: savedContactRecord[DatabaseNameStrings.fieldLocalContactIdentifier], contact: savedContactRecord[DatabaseNameStrings.fieldVCardContactData], vCardUnarchiveError: nil, contactSaveError: nil, setLocalContainerIdentifierFieldOfContactRecordError: nil, saveRecordError: nil) } } }
这是Xcode 13.4 搭载的Swift 5.6编译器的固有类型推断bug,和代码逻辑、元组定义、CloudKit表结构均无关系:
- 该bug出现在ObjC桥接的闭包上下文(CloudKit的操作回调是从ObjC框架导入Swift的)中:当在闭包内对
Result类型做switch匹配取出关联值,同时在同一段代码内调用CKRecord的ObjC桥接下标方法时,编译器的类型推导链路会失效 - 编译器会错误地给
savedContactRecord(原本应为CKRecord类型)包裹一层冗余的单元素元组包装,也就是错误提示里显示的(CKRecord),这层括号不是代码书写导致的,是编译器类型推导阶段错误生成的类型标记 - 类型推导失效还会导致编译器无法正确识别
CKRecord下标返回值的桥接类型,会把返回值识别为CloudKit框架内部的不透明类型__CKRecordObjCValue?,同时把传入的nil字面量识别为_const Error?这类内部类型,无法匹配元组别名里声明的CNContact?、Error?类型,最终报类型不匹配错误。
内容的提问来源于stack exchange,提问作者daniel

