Gnomon
另一个网络库?
我们为什么决定开发一个新的网络库呢?
-
HTTP 缓存以获得更好的用户体验。显示过时内容比显示无限加载指示符要好。为了实现这一点,Gnomon 可以执行双请求:首先请求接收缓存的(可选)内容,第二次 HTTP 调用。此外,即使没有互联网连接,库也会返回缓存版本。
-
RxSwift 接口。我们决定,使用 Rx 可观察对象来处理此类复合响应要容易得多:第一个
.next
事件携带缓存响应,第二个 – 来自网页服务器的实际响应。 -
将模型作为请求数据的结果而不是数据。这是应用程序接收到数据后的常规步骤 - 将其解析到某个模型中。我们发现让库用户跳过这个阶段更好:每个请求都有一个通用的参数 - 响应模型类型。结果订阅者接收模型或模型的数组,而不是原始数据。
安装
pod 'Gnomon', '~> 3.0'
使用说明
定义模型
我们定义了一个灵活的通用协议 BaseModel
以及几个扩展 (JSONModel
, XMLModel
, DecodableModel
)。
在大多数情况下,我们使用 JSONModel
,它使用 SwiftyJSON 作为解析器,提供了一个轻量级的接口来解析模型属性。
import Gnomon
import SwiftyJSON
struct UserModel: JSONModel {
let id: Int
let login: String
let avatarUrl: URL?
let profileUrl: URL?
init(_ json: JSON) throws {
id = json["id"].intValue
login = json["login"].stringValue
avatarUrl = json["avatar_url"].url
profileUrl = json["html_url"].url
}
}
准备一个请求
我们有一个灵活的 RequestBuilder
,它只需要 URL 字符串参数,但提供了一个构建器接口来构建复杂的 HTTP 请求。
库中有 4 种 Result
类型 – 它们配置了库如何解析您的响应
SingleResult<T>
单个模型T
,如果解析失败,请求将失败MultipleResults<T>
模型数组T
,如果解析失败,请求将失败SingleOptionalResult<T>
单个可选模型T?
,如果解析失败,请求返回nil
MultipleOptionalResults<T>
可选模型数组T?
,如果解析失败,请求返回包含模型和nil
的混合数组
func prepareRequest() throws -> Request<MultipleOptionalResults<UserModel>> {
return try RequestBuilder()
.setURLString("https://api.github.com/users").build()
}
此 API 调用返回字典数组,但我们经常遇到只解析 JSON 的特定部分的多层 JSON 返回调用。在这种情况下,您可以在构建器上添加 setXPath()
,路径由 /
分隔(例如 "document/result/data"
)。
创建一个可观察对象并订阅它
如我们上面所述,Gnomon 接口基于 RxSwift – 当你想要进行请求时,你需要创建一个可观察的对象并然后订阅它。
import RxSwift
let disposeBag = DisposeBag()
func loadData() {
do {
let request = try prepareRequest()
Gnomon.cachedThenFetch(request).subscribe(onNext: { response in
print(response.result.models)
}).disposed(by: disposeBag)
} catch {
print("can't prepare request: \(error)")
}
}
第一次运行此代码时,我们将在 stdout 中看到两条日志:空数组和 UserModel
选项数组。第一次调用后,URLSession 将响应存储在共享的 URLCache 中,在下一次调用中,我们将收到两次相同的数组。
如果你收到作为第二次响应的缓存版本,你可以通过省略 UI 更新来优化你的 UI 更新逻辑。这是由于 URLSession 内部的逻辑:它自己检查缓存到期/有效性和返回两次缓存响应。你可以通过 response.responseType
属性检测 HTTP 缓存结果。
import RxSwift
let disposeBag = DisposeBag()
func loadData() {
do {
let request = try prepareRequest()
Gnomon.cachedThenFetch(request).subscribe(onNext: { [weak self] response in
guard response.responseType != .httpCache else { return }
self?.updateUI(with: response.result.models)
}).disposed(by: disposeBag)
} catch {
print("can't prepare request: \(error)")
}
}
func updateUI(with models: [UserModel?]) {}
天文观测仪
日晷是天文观测仪最好的朋友:)
你可以使用天文观测仪和 Loaders 轻松地将你的模型转换为适用于 UITableView 或 UICollectionView 的单元格。
import Astrolabe
import Gnomon
class UsersViewController: UIViewController, Loader {
let tableView = TableView<LoaderDecoratorSource<TableViewSource>>
override func viewDidLoad() {
super.viewDidLoad()
tableView.source.loader = self
}
func load(for intent: LoaderIntent) -> SectionObservable? {
return Astrolabe.load(pLoader: self, intent: intent)
}
}
extension UsersViewController: PLoader {
typealias PLResult = MultipleOptionalResults<UserModel>
func request(for loadingIntent: LoaderIntent) throws -> Request<PLResult> {
return try RequestBuilder()
.setURLString("https://api.github.com/users").build()
}
func sections(from result: PLResult, loadingIntent: LoaderIntent) -> [Sectionable]? {
let users = result.models.flatMap { $0 }
return [Section(cells: users.map { TableCell<UserTableViewCell>(data: $0) })]
}
}