使用 DTO 处理 SwiftData 中的网络请求
了解如何使用 DTO 解耦网络请求,避免直接发送 SwiftData 对象。

如果你使用 SwiftData 进行本地数据持久化,如何处理网络请求,并使用网络响应更新本地 SwiftData 对象?——这就是这篇文章将会讨论的主题——解耦网络请求和本地数据转换的最佳实践方案。
使用 DTO 数据传输对象
根据 Swift 编程的最佳实践,在处理网络数据和本地持久化时,应当创建独立的 DTO(Data Transfer Object) 模式来处理数据解码,而不是直接让 SwiftData 模型遵循 Codable 协议。
在之前的这篇文章中,我已经介绍过了什么是 DTO:

由于 SwiftData 不支持 Sendable 协议,在跨线程传递 SwiftData 对象时可能引发数据竞争问题,因此 SwiftData 模型不适合直接用于网络传输。

通过解耦网络处理与数据持久化,网络层仅负责发送请求并返回结果(以独立的 Struct 保存),随后利用专门的 DTO 层将 Struct 转换为 SwiftData 对象,这样可以避免数据并发问题。
苹果 DataCache 示例项目做法
苹果开发者网站提供了 DataCache 示例项目,演示了如何通过网络请求获取数据,并在本地使用 SwiftData 进行数据持久化:

这个示例项目就使用了 DTO 模式来处理网络数据和本地持久化。
- 项目首先创建了 Quake 数据模型,但并没有实现 Codable 协议:
@Model
class Quake {
/// A unique identifier associated with each earthquake event.
@Attribute(.unique) var code: String
/// The measured strength of the earthquake.
var magnitude: Double
/// When the earthquake happened.
var time: Date
/// Where the earthquake happened.
var location: Location
/// Creates a new earthquake from the specified values.
init(
code: String,
magnitude: Double,
time: Date,
name: String,
longitude: Double,
latitude: Double
) {
self.code = code
self.magnitude = magnitude
self.time = time
self.location = Location(name: name, longitude: longitude, latitude: latitude)
}
}
- 然后创建了专门的 DTO 层(GeoFeatureCollection),用于保存从服务器响应的数据。
struct GeoFeatureCollection: Decodable {
let features: [Feature]
struct Feature: Decodable {
let properties: Properties
let geometry: Geometry
struct Properties: Decodable {
let mag: Double
let place: String
let time: Date
let code: String
}
struct Geometry: Decodable {
let coordinates: [Double]
}
}
}
GeoFeatureCollection 和其内部的 Feature 结构体作为网络数据的 DTO,完全匹配 从服务器返回的 JSON 结构——这样可以方便后续转换成 SwiftData 时,可以任意调整使用哪些数据。
- 在 DTO(GeoFeatureCollection)中创建了扩展方法,用于从服务端获取数据,并返回 GeoFeatureCollection 结构体的格式:

但这里不符合最佳实践。一般来说,DTO 应该只负责数据结构定义和序列化。
一个更好的实现方式是,创建一个专门的网络服务层负责处理网络请求,并 -> GeoFeatureCollection 对象:
// NetworkService.swift
class QuakeNetworkService {
func fetchQuakes() async throws -> GeoFeatureCollection {
let url = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson")!
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkError.invalidResponse
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
return try decoder.decode(GeoFeatureCollection.self, from: data)
}
}
- 最后创建了一个
Quake+GeoFeatureCollection
文件,在这个文件中,分别为 Quake 和 GeoFeatureCollection 创建了一个扩展方法,用于从服务器获取数据并创建一个 Quake 对象:
