You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中正确编码解码CNMutableContact数组的方法求助

问题分析与解决方案

你当前编码方法存在两个核心问题:

  1. postalAddresses编码丢失结构化数据:使用CNPostalAddressFormatter将地址转为人类可读的格式化字符串,导致解码时无法还原为CNPostalAddress对象,这是解码报错的直接原因。
  2. 数组转字典的存储方式有缺陷:若存在相同标签的条目,后续条目会覆盖前面的,丢失数据;且字典结构无法保留原数组的顺序。

以下是修正后的完整实现方案:


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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 20:50:25