用Swift实现基本网络通讯 – PART 2
BASIC NETWORKING IN SWIFT – PART 2
上一讲中我们已经实现了基本的API请求,并返回了正确数据。这次我们需要吧数据做一些筛选和处理,如时间的格式化和提取正确的字段内容显示在UI上。同时我们也会在之前的基础上对请求数据的方法做进一步的优化。
首先我们需要吧WeatherEngine定义为一个类,以便在UI界面中调用它。定义类方式:
// WeatherEngine.swift
// SwiftNetworking
import Foundation
class WeatherEngine {
//Functions here
//......
}
同时我们需要对fetchWeatherData()这个方法进行一些优化,大体结构是不改变的,但我们添加了closure的方式。这有点类似Objective-C中的Block。但在Swift中,Closure更为灵活和强大。关于Closure的详细介绍我们放在未来的教学中慢慢讲解。
同时我们还需要建立一个新的Swift类用以储存和传递返回的天气数据。以我个人的习惯我还是偏好建立一个单独的文件,这样便于未来代码的管理和维护。新建一个Swift文件WeatherData.swift,在其中定义WeatherData类,这里作为教学,我们就暂时只处理返回JSON中两个字段,分别是:weather数组中第一个对象中的main(天气描述,如多云,晴朗等),和main中的feels_like(体感温度)。
//...........
"weather": [
{
"id": 804,
"main": "Clouds",
"description": "overcast clouds",
"icon": "04n"
}
],
//...........
"main": {
"temp": 293.48,
"feels_like": 293.81,
"temp_min": 292.32,
"temp_max": 294.04,
"pressure": 1007,
"humidity": 86
},
//...........
WeatherData.swift
Swift
//
// WeatherData.swift
// SwiftNetworking
import Foundation
class WeatherData {
//定义变量
var weatherMain: String
var feelsLike: Double
//初始化Weather对象
init(weatherMain: String, feelsLike: Double) {
self.weatherMain = weatherMain
self.feelsLike = feelsLike
}
}
下面我重写fetchWeatherData()方法。为了让代码看起来整洁一下,我们把API地址和参数定义成变量,这样也是为了将来通过GPS自动获取地址,通过用户参数显示不同的天气数据做准备。同时再加一个参数,让请求回的单位为公制。API参数为:units=metric
// WeatherEngine.swift
// SwiftNetworking
import Foundation
//Start of tutorial Part - 2
var API_LAT = "35.18147"//未来将通过CoreLocation获取
var API_LON = "136.90641"//未来将通过CoreLocation获取
var API_UNITS = "metric" //公制单位
var API_URL = "https://api.openweathermap.org/data/2.5/weather?lat=\(API_LAT)&lon=\(API_LON)&units=\(API_UNITS)&appid=\(API_KEY)" //合成请求URL
class WeatherEngine {
//添加completion使方法返回WeatherData对象,或作异常处理
func fetchWeathData(completion: @escaping (Result<WeatherData, Error>) -> Void) {
//定义url并处理异常
guard let url = URL(string: API_URL) else {
completion(.failure(NSError(domain: "Invalid URL", code: 0, userInfo: nil)))
return
}
//定义datatask
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
//如果出现异常直接返回
if let error = error {
completion(.failure(error))
return
}
//定义http响应对象和异常处理
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(NSError(domain: "Invalid response", code: 0)))
return
}
//如果返回状态码200(OK)则开始处理返回数据
if httpResponse.statusCode == 200 {
guard let data = data else {
completion(.failure(NSError(domain: "No data", code: 0)))
return
}
do {
//创建WeatherData对象实例weather,并将解出的字段赋予给weather
if let weatherData = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
if let weatherArray = weatherData["weather"] as? [[String: Any]],
let mainData = weatherData["main"] as? [String: Any],
let weatherMain = weatherArray.first?["main"] as? String,
let feelsLike = mainData["feels_like"] as? Double {
let weather = WeatherData(weatherMain: weatherMain, feelsLike: feelsLike)
completion(.success(weather))
} else {
completion(.failure(NSError(domain: "Invalid weather data", code: 0)))
}
} else {
completion(.failure(NSError(domain: "Invalid weather data", code: 0)))
}
} catch {
completion(.failure(error))
}
} else {
completion(.failure(NSError(domain: "API request with code\(httpResponse.statusCode)", code: httpResponse.statusCode)))
}
}
//执行datatask
task.resume()
}
//测试Log出API_URL地址,本例中没有调用。
func apiUrlCheck(){
print(API_URL)
}
}
//End of tutorial Part - 2
其中这里需要注意一下:
//取出weather数组
let weatherArray = weatherData["weather"] as? [[String: Any]]
//取出weatherArray中第一个字典对象,并取出“main”字段内容
let weatherMain = weatherArray.first?["main"] as? String
从返回的JSON中我们可以看到weather这个key里面,存储的是一个数据,这里比较容易粗心忽略。在这个例子中我们请求的是当前天气,所以数组中只有一个字点对象,如果我们请求未来5天天气等情况下,数组里面将会有多个对象。
其他取值和赋值的方式跟Objective-C没有太大的差异,熟悉一下Swift的语法即可适应。
到现在我们已经完整重写了fetchWeatherData()方法。接下来我们到SwiftUI中重构用户界面,让屏幕上显示当前气象类型和体感温度。
ContentView.swift:
// ContentView.swift
// SwiftNetworking
import SwiftUI
struct ContentView: View {
//建立WeatherEngine实例
let weatherEngine = WeatherEngine()
//建立两个@State字符串变量用以储存“天气类型”和“体感温度”
@State var weatherMain = "Weather"
@State var feelsLike = "Temp"
var body: some View {
VStack {
//显示体感温度
Text(String(feelsLike))
.font(.system(size: 100, weight: .light, design: .serif))
.multilineTextAlignment(.center)
//显示天气类型
Text(weatherMain)
.font(.system(size: 24))
//测试打印API地址
Button(action: {
weatherEngine.apiUrlCheck()
}) {
Text("Fetch Weather Data")
.padding()
.background(Color.gray)
.foregroundColor(.white)
.cornerRadius(10)
}
}
//是显示时调用fetchWeathData()方法
.onAppear(){
weatherEngine.fetchWeathData { result in
switch result {
case.success(let weatherData):
print(weatherData.weatherMain)
print(weatherData.feelsLike)
self.weatherMain = weatherData.weatherMain
self.feelsLike = String(Int(weatherData.feelsLike)) + "°C"
case.failure(let error):
print("Error fetching weather data: \(error.localizedDescription)")
}
}
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
唯一需要说明的是,这里定义变量时用到了@State var weatherMain = “Weather”
在 SwiftUI 中,视图是由数据(状态)驱动的。每当视图在创建或解析时,都会为该视图和该视图中使用的状态数据之间创建一个依赖关系,每当状态的信息发生变化,有依赖关系的视图会马上翻译出这些变化并重绘。
好了,本章结束。下一章,我们将会获取更多数据并开始SwiftUI构造界面的讲解。
Happy coding!