开发一个天气应用时,界面要显示城市名、温度、湿度,还有一周预报。这些数据从哪来?怎么组织?很多人一上来就急着写界面,结果代码越堆越多,改个接口字段全得重来。其实关键在于Model层的写法。
Model不是简单的数据容器
很多人以为Model就是定义几个属性,比如 cityName、temperature,然后在ViewModel里直接调用API返回的数据塞进去。这种做法看似简单,实则埋下隐患。一旦接口字段变了,或者需要对数据做处理,就得到处改。
真正的Model应该封装数据来源和逻辑。它不关心界面长什么样,只负责把干净、结构化的数据交给ViewModel。
用类来组织,别用字典凑合
比如天气数据,应该定义一个 WeatherModel 类:
class WeatherModel {
let cityName: String
let temperature: Double
let humidity: Int
let forecast: [ForecastItem]
init?(from json: [String: Any]) {
guard let name = json["city"] as? String,
let temp = json["temp_c"] as? Double else {
return nil
}
self.cityName = name
self.temperature = temp
self.humidity = json["humidity"] as? Int ?? 0
if let list = json["forecast"] as? [[String: Any]] {
self.forecast = list.compactMap { ForecastItem(from: $0) }
} else {
self.forecast = []
}
}
}
这样做的好处是,接口字段叫 temp_c 还是 temperature,对上层透明。ViewModel 拿到的就是标准结构,不需要知道原始数据长啥样。
网络请求不该出现在ViewModel
有些开发者喜欢在ViewModel里直接发请求,解析完塞进属性。这会让ViewModel越来越臃肿。正确的做法是,把请求逻辑放在Model层的服务类里。
比如加一个 WeatherService:
class WeatherService {
func fetchWeather(for city: String, completion: @escaping (WeatherModel?) -> Void) {
let url = URL(string: "https://api.weather.com/v1/forecast?city=\(city)")!
URLSession.shared.dataTask(with: url) { data, _, _ in
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
completion(nil)
return
}
let model = WeatherModel(from: json)
completion(model)
}.resume()
}
}
ViewModel只需要调用 service.fetchWeather(...),拿到结果后更新状态就行。职责清晰,测试也方便。
数据转换放Model,别让ViewModel操心
比如温度单位要从摄氏转华氏,有人会在ViewModel里写转换逻辑。其实这属于数据处理,应该由Model提供计算属性:
var temperatureFahrenheit: Double {
return temperature * 9 / 5 + 32
}
或者日期格式化,也该由Model处理好再输出。ViewModel只管拿数据更新UI,不做加工。
本地缓存也可以归Model管
如果希望离线也能看上次的数据,可以在WeatherService里加一层缓存读取,优先从UserDefaults或数据库取,再异步刷新。这些细节都不需要ViewModel知道。
Model层就像后台服务员,默默把菜做好端出来,前台(ViewModel)只需摆盘上桌,不用关心锅里怎么炒的。