Swift中正确编码解码CNMutableContact数组的方法求助
问题分析与解决方案
你当前编码方法存在两个核心问题:
postalAddresses编码丢失结构化数据:使用CNPostalAddressFormatter将地址转为人类可读的格式化字符串,导致解码时无法还原为CNPostalAddress对象,这是解码报错的直接原因。- 数组转字典的存储方式有缺陷:若存在相同标签的条目,后续条目会覆盖前面的,丢失数据;且字典结构无法保留原数组的顺序。
以下是修正后的完整实现方案:
1. 定义辅助编码键
首先需要为非Codable类型(如CNPostalAddress、带标签的值)定义专属编码键:
// 外层编码键(需与你的CodingKeys合并) enum CodingKeys: String, CodingKey { case contactType case namePrefix, givenName, middleName, familyName, previousFamilyName, nameSuffix, nickname case jobTitle, departmentName, organizationName case postalAddresses, emailAddresses, urlAddresses, phoneNumbers, socialProfiles case birthday, note case id, contactIdentifier } // 结构化地址编码键 private enum PostalAddressCodingKeys: String, CodingKey { case label, street, city, state, postalCode, country, isoCountryCode } // 通用带标签字符串编码键(用于邮箱、URL) private enum LabeledStringCodingKeys: String, CodingKey { case label, value } // 电话号码编码键 private enum LabeledPhoneCodingKeys: String, CodingKey { case label, stringValue } // 社交资料编码键 private enum SocialProfileCodingKeys: String, CodingKey { case label, urlString, username, service }
2. 修正Encode函数
将数组类型的字段以结构化数组而非字典的方式编码,保留所有原始数据:
public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) // 基础字段编码(原代码保留) try container.encode(self.contact.contactType.rawValue, forKey: .contactType) try container.encode(self.contact.namePrefix, forKey: .namePrefix) try container.encode(self.contact.givenName, forKey: .givenName) try container.encode(self.contact.middleName, forKey: .middleName) try container.encode(self.contact.familyName, forKey: .familyName) try container.encode(self.contact.previousFamilyName, forKey: .previousFamilyName) try container.encode(self.contact.nameSuffix, forKey: .nameSuffix) try container.encode(self.contact.nickname, forKey: .nickname) try container.encode(self.contact.jobTitle, forKey: .jobTitle) try container.encode(self.contact.departmentName, forKey: .departmentName) try container.encode(self.contact.organizationName, forKey: .organizationName) try container.encode(self.contact.birthday, forKey: .birthday) try container.encode(self.contact.note, forKey: .note) try container.encode(id, forKey: .id) try container.encode(contactIdentifier, forKey: .contactIdentifier) // 编码 postalAddresses(结构化存储) var postalContainer = container.nestedUnkeyedContainer(forKey: .postalAddresses) for labeledAddr in self.contact.postalAddresses { var addrContainer = postalContainer.nestedContainer(keyedBy: PostalAddressCodingKeys.self) try addrContainer.encode(labeledAddr.label, forKey: .label) let postalAddr = labeledAddr.value try addrContainer.encode(postalAddr.street, forKey: .street) try addrContainer.encode(postalAddr.city, forKey: .city) try addrContainer.encode(postalAddr.state, forKey: .state) try addrContainer.encode(postalAddr.postalCode, forKey: .postalCode) try addrContainer.encode(postalAddr.country, forKey: .country) try addrContainer.encode(postalAddr.isoCountryCode, forKey: .isoCountryCode) } // 编码 emailAddresses var emailContainer = container.nestedUnkeyedContainer(forKey: .emailAddresses) for labeledEmail in self.contact.emailAddresses { var emailItemContainer = emailContainer.nestedContainer(keyedBy: LabeledStringCodingKeys.self) try emailItemContainer.encode(labeledEmail.label, forKey: .label) try emailItemContainer.encode(labeledEmail.value as String, forKey: .value) } // 编码 urlAddresses var urlContainer = container.nestedUnkeyedContainer(forKey: .urlAddresses) for labeledUrl in self.contact.urlAddresses { var urlItemContainer = urlContainer.nestedContainer(keyedBy: LabeledStringCodingKeys.self) try urlItemContainer.encode(labeledUrl.label, forKey: .label) try urlItemContainer.encode(labeledUrl.value.absoluteString, forKey: .value) } // 编码 phoneNumbers var phoneContainer = container.nestedUnkeyedContainer(forKey: .phoneNumbers) for labeledPhone in self.contact.phoneNumbers { var phoneItemContainer = phoneContainer.nestedContainer(keyedBy: LabeledPhoneCodingKeys.self) try phoneItemContainer.encode(labeledPhone.label, forKey: .label) try phoneItemContainer.encode(labeledPhone.value.stringValue, forKey: .stringValue) } // 编码 socialProfiles var socialContainer = container.nestedUnkeyedContainer(forKey: .socialProfiles) for labeledSocial in self.contact.socialProfiles { var socialItemContainer = socialContainer.nestedContainer(keyedBy: SocialProfileCodingKeys.self) try socialItemContainer.encode(labeledSocial.label, forKey: .label) let socialProfile = labeledSocial.value try socialItemContainer.encode(socialProfile.urlString, forKey: .urlString) try socialItemContainer.encode(socialProfile.username, forKey: .username) try socialItemContainer.encode(socialProfile.service, forKey: .service) } }
3. 完成Decode函数
根据编码的结构化数据,逐一还原为CNLabeledValue数组并赋值给CNMutableContact:
init(from decoder: Decoder) throws { let decodedContact = try decoder.container(keyedBy: CodingKeys.self) // 基础字段解码(原代码保留) id = try decodedContact.decode(UUID.self, forKey: .id) contactIdentifier = try decodedContact.decode(String.self, forKey: .contactIdentifier) contact = CNMutableContact() let intContactType = try decodedContact.decode(Int.self, forKey: .contactType) contact.contactType = intContactType == 0 ? .person : .organization contact.namePrefix = try decodedContact.decodeIfPresent(String.self, forKey: .namePrefix) ?? "" contact.givenName = try decodedContact.decodeIfPresent(String.self, forKey: .givenName) ?? "" contact.middleName = try decodedContact.decodeIfPresent(String.self, forKey: .middleName) ?? "" contact.familyName = try decodedContact.decodeIfPresent(String.self, forKey: .familyName) ?? "" contact.previousFamilyName = try decodedContact.decodeIfPresent(String.self, forKey: .previousFamilyName) ?? "" contact.nameSuffix = try decodedContact.decodeIfPresent(String.self, forKey: .nameSuffix) ?? "" contact.nickname = try decodedContact.decodeIfPresent(String.self, forKey: .nickname) ?? "" contact.jobTitle = try decodedContact.decodeIfPresent(String.self, forKey: .jobTitle) ?? "" contact.departmentName = try decodedContact.decodeIfPresent(String.self, forKey: .departmentName) ?? "" contact.organizationName = try decodedContact.decodeIfPresent(String.self, forKey: .organizationName) ?? "" contact.birthday = try decodedContact.decodeIfPresent(DateComponents.self, forKey: .birthday) contact.note = try decodedContact.decodeIfPresent(String.self, forKey: .note) ?? "" // 解码 postalAddresses let postalContainer = try decodedContact.nestedUnkeyedContainer(forKey: .postalAddresses) var postalAddresses: [CNLabeledValue<CNPostalAddress>] = [] while !postalContainer.isAtEnd { let addrContainer = try postalContainer.nestedContainer(keyedBy: PostalAddressCodingKeys.self) let label = try addrContainer.decodeIfPresent(String.self, forKey: .label) let postalAddr = CNMutablePostalAddress() postalAddr.street = try addrContainer.decodeIfPresent(String.self, forKey: .street) ?? "" postalAddr.city = try addrContainer.decodeIfPresent(String.self, forKey: .city) ?? "" postalAddr.state = try addrContainer.decodeIfPresent(String.self, forKey: .state) ?? "" postalAddr.postalCode = try addrContainer.decodeIfPresent(String.self, forKey: .postalCode) ?? "" postalAddr.country = try addrContainer.decodeIfPresent(String.self, forKey: .country) ?? "" postalAddr.isoCountryCode = try addrContainer.decodeIfPresent(String.self, forKey: .isoCountryCode) ?? "" postalAddresses.append(CNLabeledValue(label: label, value: postalAddr.copy() as! CNPostalAddress)) } contact.postalAddresses = postalAddresses // 解码 emailAddresses let emailContainer = try decodedContact.nestedUnkeyedContainer(forKey: .emailAddresses) var emails: [CNLabeledValue<NSString>] = [] while !emailContainer.isAtEnd { let emailItemContainer = try emailContainer.nestedContainer(keyedBy: LabeledStringCodingKeys.self) let label = try emailItemContainer.decodeIfPresent(String.self, forKey: .label) let email = try emailItemContainer.decode(String.self, forKey: .value) as NSString emails.append(CNLabeledValue(label: label, value: email)) } contact.emailAddresses = emails // 解码 urlAddresses let urlContainer = try decodedContact.nestedUnkeyedContainer(forKey: .urlAddresses) var urls: [CNLabeledValue<NSURL>] = [] while !urlContainer.isAtEnd { let urlItemContainer = try urlContainer.nestedContainer(keyedBy: LabeledStringCodingKeys.self) let label = try urlItemContainer.decodeIfPresent(String.self, forKey: .label) let urlString = try urlItemContainer.decode(String.self, forKey: .value) if let url = NSURL(string: urlString) { urls.append(CNLabeledValue(label: label, value: url)) } } contact.urlAddresses = urls // 解码 phoneNumbers let phoneContainer = try decodedContact.nestedUnkeyedContainer(forKey: .phoneNumbers) var phones: [CNLabeledValue<CNPhoneNumber>] = [] while !phoneContainer.isAtEnd { let phoneItemContainer = try phoneContainer.nestedContainer(keyedBy: LabeledPhoneCodingKeys.self) let label = try phoneItemContainer.decodeIfPresent(String.self, forKey: .label) let phoneString = try phoneItemContainer.decode(String.self, forKey: .stringValue) let phoneNumber = CNPhoneNumber(stringValue: phoneString) phones.append(CNLabeledValue(label: label, value: phoneNumber)) } contact.phoneNumbers = phones // 解码 socialProfiles let socialContainer = try decodedContact.nestedUnkeyedContainer(forKey: .socialProfiles) var socialProfiles: [CNLabeledValue<CNSocialProfile>] = [] while !socialContainer.isAtEnd { let socialItemContainer = try socialContainer.nestedContainer(keyedBy: SocialProfileCodingKeys.self) let label = try socialItemContainer.decodeIfPresent(String.self, forKey: .label) let urlString = try socialItemContainer.decodeIfPresent(String.self, forKey: .urlString) let username = try socialItemContainer.decodeIfPresent(String.self, forKey: .username) let service = try socialItemContainer.decodeIfPresent(String.self, forKey: .service) if let service = service { let socialProfile = CNSocialProfile(urlString: urlString, username: username, userIdentifier: nil, service: service) socialProfiles.append(CNLabeledValue(label: label, value: socialProfile)) } } contact.socialProfiles = socialProfiles }
关键说明
- 所有数组类型字段均以嵌套数组+结构化容器的方式编码,保留了原始数据的完整性和顺序。
- 解码时通过还原每个字段的原始属性,重新构建
CNPostalAddress、CNPhoneNumber等对象,避免了格式化字符串无法解析的问题。 - 使用
decodeIfPresent处理可选字段,避免因字段缺失导致解码失败。
内容的提问来源于stack exchange,提问作者user21104171
相关产品推荐
相关产品推荐

