SwiftUI中MKPlacemark的JSON编解码问题求助
解决MKPlacemark的JSON编解码问题
现有代码的核心错误
- 类型不匹配:
getData方法中编码的是[Landmark]数组,但解码时使用Landmark.self,直接导致JSON格式不匹配。 - 计算属性无法编码:
id是计算属性,每次访问都会生成新UUID,编码后无法还原,必须改为存储属性。 - CLLocationCoordinate2D不支持Codable:直接对该类型编解码会失败,需手动实现Codable扩展。
- 编解码逻辑错误:使用无键容器时编码和解码顺序不一致,且强制解包存在崩溃风险。
完整解决方案
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
相关产品推荐
相关产品推荐

