版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.12.13 星期四 |
前言
PromiseKit(GitHub地址) 只是 Promise 设计模式的一种实现方式。并不是我们项目中必须采用的一种方式,但是它可以增强代码的可读性和维护性,让代码更加的优雅。接下来我们就一起看一下这个框架。感兴趣的看下面几篇文章。
1. PromiseKit框架详细解析(一) —— 基本概览(一)
2. PromiseKit框架详细解析(二) —— 基于PromiseKit的天气应用的简单示例(一)
源码
1. Swift
首先看一下工程结构

下面看一下sb中的内容

下面就是代码了
1. BrokenPromise.swift
import Foundation
import PromiseKit
func brokenPromise<T>(method: String = #function) -> Promise<T> {
return Promise<T>() { seal in
let err = NSError(domain: "WeatherOrNot", code: 0, userInfo: [NSLocalizedDescriptionKey: "'\(method)' not yet implemented."])
seal.reject(err)
}
}
2. WeatherViewController.swift
import UIKit
import PromiseKit
import CoreLocation
private let errorColor = UIColor(red: 0.96, green: 0.667, blue: 0.690, alpha: 1)
private let oneHour: TimeInterval = 3600 // Seconds per hour
private let randomCities = [("Tokyo", "JP", 35.683333, 139.683333),
("Jakarta", "ID", -6.2, 106.816667),
("Delhi", "IN", 28.61, 77.23),
("Manila", "PH", 14.58, 121),
("São Paulo", "BR", -23.55, -46.633333)]
class WeatherViewController: UIViewController {
@IBOutlet private var placeLabel: UILabel!
@IBOutlet private var tempLabel: UILabel!
@IBOutlet private var iconImageView: UIImageView!
@IBOutlet private var conditionLabel: UILabel!
@IBOutlet private var randomWeatherButton: UIButton!
let weatherAPI = WeatherHelper()
let locationHelper = LocationHelper()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateWithCurrentLocation()
}
private func updateWithCurrentLocation() {
locationHelper.getLocation()
.done { [weak self] placemark in
self?.handleLocation(placemark: placemark)
}
.catch { [weak self] error in
guard let self = self else { return }
self.tempLabel.text = "--"
self.placeLabel.text = "--"
switch error {
case is CLError where (error as? CLError)?.code == .denied:
self.conditionLabel.text = "Enable Location Permissions in Settings"
self.conditionLabel.textColor = UIColor.white
default:
self.conditionLabel.text = error.localizedDescription
self.conditionLabel.textColor = errorColor
}
}
after(seconds: oneHour).done { [weak self] in
self?.updateWithCurrentLocation()
}
}
private func handleMockLocation() {
let coordinate = CLLocationCoordinate2DMake(37.966667, 23.716667)
handleLocation(city: "Athens", state: "Greece", coordinate: coordinate)
}
private func handleLocation(placemark: CLPlacemark) {
handleLocation(city: placemark.locality,
state: placemark.administrativeArea,
coordinate: placemark.location!.coordinate)
}
private func handleLocation(city: String?,
state: String?,
coordinate: CLLocationCoordinate2D) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
weatherAPI.getWeather(atLatitude: coordinate.latitude,
longitude: coordinate.longitude)
.then { [weak self] weatherInfo -> Promise<UIImage> in
guard let self = self else { return brokenPromise() }
self.updateUI(with: weatherInfo)
return self.weatherAPI.getIcon(named: weatherInfo.weather.first!.icon)
}
.done(on: DispatchQueue.main) { icon in
self.iconImageView.image = icon
}
.catch { error in
self.tempLabel.text = "--"
self.conditionLabel.text = error.localizedDescription
self.conditionLabel.textColor = errorColor
}
.finally {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
private func updateUI(with weatherInfo: WeatherHelper.WeatherInfo) {
let tempMeasurement = Measurement(value: weatherInfo.main.temp, unit: UnitTemperature.kelvin)
let formatter = MeasurementFormatter()
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .none
formatter.numberFormatter = numberFormatter
let tempStr = formatter.string(from: tempMeasurement)
self.tempLabel.text = tempStr
self.placeLabel.text = weatherInfo.name
self.conditionLabel.text = weatherInfo.weather.first?.description ?? "empty"
self.conditionLabel.textColor = UIColor.white
}
@IBAction func showRandomWeather(_ sender: AnyObject) {
randomWeatherButton.isEnabled = false
let weatherPromises = randomCities.map { weatherAPI.getWeather(atLatitude: $0.2, longitude: $0.3) }
UIApplication.shared.isNetworkActivityIndicatorVisible = true
race(weatherPromises)
.then { [weak self] weatherInfo -> Promise<UIImage> in
guard let self = self else { return brokenPromise() }
self.placeLabel.text = weatherInfo.name
self.updateUI(with: weatherInfo)
return self.weatherAPI.getIcon(named: weatherInfo.weather.first!.icon)
}
.done { icon in
self.iconImageView.image = icon
}
.catch { error in
self.tempLabel.text = "--"
self.conditionLabel.text = error.localizedDescription
self.conditionLabel.textColor = errorColor
}
.finally {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
self.randomWeatherButton.isEnabled = true
}
}
}
// MARK: - UITextFieldDelegate
extension WeatherViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
guard let text = textField.text else { return false }
locationHelper.searchForPlacemark(text: text)
.done { placemark in
self.handleLocation(placemark: placemark)
}
.catch { _ in }
return true
}
}
3. LocationHelper.swift
import Foundation
import CoreLocation
import PromiseKit
class LocationHelper {
let coder = CLGeocoder()
func getLocation() -> Promise<CLPlacemark> {
return CLLocationManager.requestLocation().lastValue.then { location in
return self.coder.reverseGeocode(location: location).firstValue
}
}
func searchForPlacemark(text: String) -> Promise<CLPlacemark> {
return coder.geocode(text).firstValue
}
}
4. WeatherHelper.swift
import Foundation
import PromiseKit
private let appID = "Enter Your API Key from http://openweathermap.org/appid"
class WeatherHelper {
struct WeatherInfo: Codable {
let main: Temperature
let weather: [Weather]
var name: String = "Error: invalid jsonDictionary! Verify your appID is correct"
}
struct Weather: Codable {
let icon: String
let description: String
}
struct Temperature: Codable {
let temp: Double
}
func getWeatherTheOldFashionedWay(coordinate: CLLocationCoordinate2D, completion: @escaping (WeatherInfo?, Error?) -> Void) {
let urlString = "http://api.openweathermap.org/data/2.5/weather?lat=\(coordinate.latitude)&lon=\(coordinate.longitude)&appid=\(appID)"
guard let url = URL(string: urlString) else {
preconditionFailure("Failed creating API URL - Make sure you set your OpenWeather API Key")
}
URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data,
let result = try? JSONDecoder().decode(WeatherInfo.self, from: data) else {
completion(nil, error)
return
}
completion(result, nil)
}.resume()
}
func getWeather(atLatitude latitude: Double, longitude: Double) -> Promise<WeatherInfo> {
let urlString = "http://api.openweathermap.org/data/2.5/weather?lat=" +
"\(latitude)&lon=\(longitude)&appid=\(appID)"
let url = URL(string: urlString)!
return firstly {
URLSession.shared.dataTask(.promise, with: url)
}.compactMap {
return try JSONDecoder().decode(WeatherInfo.self, from: $0.data)
}
}
func getIcon(named iconName: String) -> Promise<UIImage> {
return Promise<UIImage> {
getFile(named: iconName, completion: $0.resolve)
}
.recover { _ in
self.getIconFromNetwork(named: iconName)
}
}
func getIconFromNetwork(named iconName: String) -> Promise<UIImage> {
let urlString = "http://openweathermap.org/img/w/\(iconName).png"
let url = URL(string: urlString)!
return firstly {
URLSession.shared.dataTask(.promise, with: url)
}
.then(on: DispatchQueue.global(qos: .background)) { urlResponse in
return Promise {
self.saveFile(named: iconName, data: urlResponse.data, completion: $0.resolve)
}
.then(on: DispatchQueue.global(qos: .background)) {
return Promise.value(UIImage(data: urlResponse.data)!)
}
}
}
private func saveFile(named: String, data: Data, completion: @escaping (Error?) -> Void) {
guard let path = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent(named+".png") else { return }
DispatchQueue.global(qos: .background).async {
do {
try data.write(to: path)
print("Saved image to: " + path.absoluteString)
completion(nil)
} catch {
completion(error)
}
}
}
private func getFile(named: String, completion: @escaping (UIImage?, Error?) -> Void) {
DispatchQueue.global(qos: .background).async {
if let path = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent(named+".png"),
let data = try? Data(contentsOf: path),
let image = UIImage(data: data) {
DispatchQueue.main.async { completion(image, nil) }
} else {
let error = NSError(domain: "WeatherOrNot",
code: 0,
userInfo: [NSLocalizedDescriptionKey: "Image file '\(named)' not found."])
DispatchQueue.main.async { completion(nil, error) }
}
}
}
}
后记
本篇主要讲述了基于PromiseKit的天气应用的简单示例,感兴趣的给个赞或者关注~~~
