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

SwiftUI中MKPlacemark的JSON编解码问题求助

解决MKPlacemark的JSON编解码问题

现有代码的核心错误

  1. 类型不匹配:getData方法中编码的是[Landmark]数组,但解码时使用Landmark.self,直接导致JSON格式不匹配。
  2. 计算属性无法编码:id是计算属性,每次访问都会生成新UUID,编码后无法还原,必须改为存储属性。
  3. CLLocationCoordinate2D不支持Codable:直接对该类型编解码会失败,需手动实现Codable扩展。
  4. 编解码逻辑错误:使用无键容器时编码和解码顺序不一致,且强制解包存在崩溃风险。

完整解决方案

1. 修正Landmark结构体

将id改为存储属性,确保编码后能正确还原:

import Foundation 
import MapKit 
import UIKit

struct Landmark {
    let placemark: MKPlacemark
    let id: UUID // 改为存储属性,初始化时生成
    
    var name: String {
        placemark.name ?? ""
    }
    
    var title: String {
        placemark.title ?? ""
    }
    
    var coordinate: CLLocationCoordinate2D {
        placemark.coordinate
    }
    
    // 自定义初始化方法
    init(placemark: MKPlacemark) {
        self.placemark = placemark
        self.id = UUID()
    }
}

2. 给CLLocationCoordinate2D添加Codable支持

extension CLLocationCoordinate2D: Codable {
    enum CodingKeys: String, CodingKey {
        case latitude, longitude
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(latitude, forKey: .latitude)
        try container.encode(longitude, forKey: .longitude)
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        latitude = try container.decode(Double.self, forKey: .latitude)
        longitude = try container.decode(Double.self, forKey: .longitude)
    }
}

3. 实现Landmark的Codable扩展(利用归档处理MKPlacemark)

通过NSSecureCoding将MKPlacemark转为Data,再进行JSON编解码,无需知晓其内部所有键:

// 通用归档/解归档工具扩展
extension NSSecureCoding {
    func archived() throws -> Data {
        try NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false)
    }
}

extension Data {
    func unarchived<T: NSSecureCoding>() throws -> T? {
        try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(self) as? T
    }
}

extension Landmark: Codable {
    enum CodingKeys: String, CodingKey {
        case placemarkData, id
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        // 将MKPlacemark归档为Data后编码
        try container.encode(placemark.archived(), forKey: .placemarkData)
        try container.encode(id, forKey: .id)
        // name、title、coordinate是计算属性,无需编码,从placemark直接获取
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        // 解码Data并还原MKPlacemark
        let placemarkData = try container.decode(Data.self, forKey: .placemarkData)
        guard let placemark = try placemarkData.unarchived() else {
            throw DecodingError.dataCorruptedError(
                forKey: .placemarkData, 
                in: container, 
                debugDescription: "无法还原MKPlacemark"
            )
        }
        self.placemark = placemark
        self.id = try container.decode(UUID.self, forKey: .id)
    }
}

4. 修正getData方法的解码逻辑

编码的是数组,解码时需对应使用数组类型:

private func getData() async {
    do {
        let landmarkData = try JSONEncoder().encode(self.landmarks)
        let decodedLandmarks = try JSONDecoder().decode([Landmark].self, from: landmarkData)
        print("解码地标数量:\(decodedLandmarks.count)")
        print("第一个地标ID:\(decodedLandmarks.first?.id ?? UUID())")
    } catch {
        print("错误:\(error)")
    }
}

原理说明

MKPlacemark是MapKit框架中的Objective-C类,遵循NSSecureCoding而非Swift的Codable协议。通过将其归档为二进制Data,再把这个Data作为Codable结构体的字段进行JSON编解码,即可绕过“未知内部键”的问题,利用系统提供的归档能力完成数据的序列化与反序列化。

内容的提问来源于stack exchange,提问作者Valentina Carrington

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 10:46:20