测试已测试 | ✗ |
语言语言 | Obj-CObjective C |
许可 | 自定义 |
发布上次发布 | 2017年2月 |
SwiftSwift 版本 | 3.0 |
由 Lutfi 维护。
依赖项 | |
Siesta | >= 0 |
XCGLogger | >= 0 |
编写 iOS / macOS REST 客户端的优雅方式
通过提供可观察模型的客户端缓存来大大简化应用程序代码。
swift-2.x
分支以获得旧版支持)想让您的应用程序与远程 API 通信?欢迎来到您的噩梦状态!
当响应数据到达时,您需要显示响应数据。除非请求的屏幕不再可见。除非其他当前可见的 UI 碎片偶然需要相同的数据。或即将需要。
您应该显示一个加载指示器(但留意可能导致无限旋转的竞争条件),显示用户友好的错误(但不要多余 —— 没有模态警报狗堆!),为用户提供重试机制……并在随后的请求成功时隐藏所有这些。
务必避免重复请求 —— 并且避免重复响应反序列化。反序列化应该在后台线程上进行,当然。哦,并且记住不要意外地在回调闭包中保留 ViewController / 模型 / 辅助器等东西。除非您应该这么做。
自然地,您会希望为每个创建的项目从头开始以稍微不同的方式重写所有这些。
可能发生什么错误?
Siesta通过提供一种针对熟悉的以请求为中心的方案的资源为中心的替代方案,结束了这个头疼的问题。
Siesta提供了一个应用范围内的RESTful资源状态的观察模型。此模型回答了三个基本问题
……,并在这些问题的答案改变时广播通知。
Siesta处理所有转换和边缘情况,以包装并交付这些答案,让您专注于您的逻辑和UI。
URLSession
,或Alamofire
,或注入您自己的custom adapter
)。该项目始于我们在几个Bust Out Solutions项目中的实际需求下编写的辅助代码。当我们发现自己需要在项目之间复制代码时,我们知道是时候开源了。
对于开源过渡,我们花了时间用Swift重写我们的代码——并以Swift的思维方式重写它,拥抱这门语言以使API尽可能简洁。
因此,Siesta的代码既是旧的也是新的:在App Store上经过战斗考验,然后在一个Swift风格的空白场地上重获新生。
让默认的事情在大多数时候是正确的。
让正确的事情始终简单。
从需求出发。不要为了解决问题而发明解决方案。
设计API时以以下目标为导向
……按照这个优先级顺序。
Siesta需要Swift 3,因此请确保您安装了Xcode 8。(如果您还没有完成大型迁移,请使用swift-2.x
分支。)
将Siesta作为一个子模块克隆到您选择的目录中,本例中为Libraries/Siesta。
git submodule add https://github.com/bustoutsolutions/siesta.git Libraries/Siesta
git submodule update --init
将Siesta.xcodeproj
拖到您的项目树中,成为子项目。
在您的项目的构建阶段下,展开目标依赖项。点击加号按钮,添加Siesta。
展开“链接二进制库”阶段。点击加号按钮,添加Siesta。
在左上角点击加号按钮,添加一个复制文件构建阶段。将目录设置为Frameworks。点击加号按钮,添加Siesta。
如果您想使用UI辅助工具,需要重复步骤3-5以添加SiestaUI
。
请告诉我们,即使您最终搞清楚也是如此。了解人们在哪些地方遇到困难将有助于改进这些说明!
为需要使用的REST API创建一个共享服务实例
let MyAPI = Service(baseURL: "https://api.example.com")
现在注册您的视图控制器——或者视图、内部胶水类、响应式信号/序列,您喜欢的一切——以便在特定资源的状态改变时接收通知
override func viewDidLoad() {
super.viewDidLoad()
MyAPI.resource("/profile").addObserver(self)
}
使用这些通知来填充您的UI
func resourceChanged(_ resource: Resource, event: ResourceEvent) {
nameLabel.text = resource.jsonDict["name"] as? String
colorLabel.text = resource.jsonDict["favoriteColor"] as? String
errorLabel.text = resource.latestError?.userMessage
}
或者如果您不喜欢代理,Siesta支持闭包观察者
MyAPI.resource("/profile").addObserver(owner: self) {
[weak self] resource, _ in
self?.nameLabel.text = resource.jsonDict["name"] as? String
self?.colorLabel.text = resource.jsonDict["favoriteColor"] as? String
self?.errorLabel.text = resource.latestError?.userMessage
}
请注意,当我们调用jsonDict
时,并不会发生实际的JSON解析。JSON已经被解析,在主线程外的GCD队列中——而且与其它框架不同,它只会解析一次,无论观察者有多少。
当然,您可能不想在所有控制器上工作都使用原始JSON。您可以配置Siesta自动将原始响应转换为模型
MyAPI.configureTransformer("/profile") { // Path supports wildcards
UserProfile(json: $0.content) // Create models however you like
}
……现在观察者看到的是模型,而不是JSON
MyAPI.resource("/profile").addObserver(owner: self) {
[weak self] resource, _ in
self?.showProfile(resource.typedContent()) // Response now contains UserProfile instead of JSON
}
func showProfile(profile: UserProfile?) {
...
}
在视图出现时触发一个感知过时,抑制冗余请求的加载操作
override func viewWillAppear(_ animated: Bool) {
MyAPI.resource("/profile").loadIfNeeded()
}
……现在您有了联网的UI。
添加一个加载指示器
MyAPI.resource("/profile").addObserver(owner: self) {
[weak self] resource, event in
self?.activityIndicator.isHidden = !resource.isLoading
}
……或者最好使用Siesta预制的ResourceStatusOverlay
视图以免费获取活动指示器、格式化的错误消息和重试按钮
class ProfileViewController: UIViewController, ResourceObserver {
@IBOutlet weak var nameLabel, colorLabel: UILabel!
@IBOutlet weak var statusOverlay: ResourceStatusOverlay!
override func viewDidLoad() {
super.viewDidLoad()
MyAPI.resource("/profile")
.addObserver(self)
.addObserver(statusOverlay)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
MyAPI.resource("/profile").loadIfNeeded()
}
func resourceChanged(_ resource: Resource, event: ResourceEvent) {
nameLabel.text = resource.jsonDict["name"] as? String
colorLabel.text = resource.jsonDict["favoriteColor"] as? String
}
}
请注意,此示例不是玩具代码。连同它的故事板,这个小类就是一个全副武装、功能齐全的REST支持的UI。
看看AFNetworking著名的用于异步加载和缓存远程图像的UIImageView
扩展。当真,去看看这段代码并消化一下它所做的所有酷事情。花几分钟时间。我会等的。我是一个README。我不会走开。
明白了?很好。
这是您如何使用Siesta实现相同功能的方法
class RemoteImageView: UIImageView {
static var imageCache: Service = Service()
var placeholderImage: UIImage?
var imageURL: URL? {
get { return imageResource?.url }
set { imageResource = RemoteImageView.imageCache.resource(absoluteURL: newValue) }
}
var imageResource: Resource? {
willSet {
imageResource?.removeObservers(ownedBy: self)
imageResource?.cancelLoadIfUnobserved(afterDelay: 0.05)
}
didSet {
imageResource?.loadIfNeeded()
imageResource?.addObserver(owner: self) { [weak self] _ in
self?.image = self?.imageResource?.typedContent(
ifNone: self?.placeholderImage)
}
}
}
}
两个版本的小图片,供您比较代码
相同的功能。是的,真的。
(好吧,好吧,它们并不完全一样。Siesta版本具有更健壮的缓存行为,并且如果它被刷新的话,会自动更新所有显示的图像。)
在Siesta中已经包含了一个更加功能丰富的RemoteImageView
版本(可在此找到Siesta的远程视图图像API),但这并不是重点。"代码更少"也并不是重点。重点是Siesta提供了一种优雅的抽象,它能够解决你真正遇到的问题,使你的代码更简单、更不易出错。
流行的REST/网络框架有不同的主要目标
URLSession
提供了一个Swifty、现代感包装器。哪一个适合你的项目?这取决于你的需求和品味。
Siesta具有强大的功能,但并不试图解决所有问题。特别是Moya和RestKit解决了互补/替代问题,而Alamofire和AFNetworking提供了更强大的底层HTTP支持。进一步复杂化比较的是,一些框架建立在其他框架之上。例如,当你使用Moya时,你也在注册Alamofire。
考虑到所有这些,以下是一个功能比较¹
Siesta | Alamofire | RestKit | Moya | AFNetworking | URLSession | |
---|---|---|---|---|---|---|
HTTP请求 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
异步响应回调 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
可观察的内存缓存 | ✓ | |||||
防止冗余请求 | ✓ | |||||
防止冗余解析 | ✓ | |||||
常见格式的解析 | ✓ | ✓ | ✓ | |||
基于路由的解析 | ✓ | ✓ | ||||
基于内容类型的解析 | ✓ | |||||
文件上传/下载任务 | ✓ | ~ | ✓ | ✓ | ||
对象模型映射 | ✓ | |||||
Core Data集成 | ✓ | |||||
隐藏HTTP | ✓ | |||||
UI助手 | ✓ | ✓ | ||||
主要语言 | Swift | Swift | Obj-C | Swift | Obj-C | Obj-C |
非简单代码行² | 2424 | 2455 | 13276 | 831 | 4025 | ? |
基于 | 任何(可注入) | URLSession | AFNetworking | Alamofire | NSURLSession / NSURLConnection | Apple核心 |
1. 声明:表格由Siesta的非全能作者编译。需要纠正/添加?请提交PR。
2. “简单”意味着只包含空白、注释、括号、分号和大括号的行。
尽管有这样一套功能列表,但Siesta是一个非常精简的代码库——与Alamofire相当重量级,而RestKit则轻85倍。
不仅仅是因为特性。Siesta解决的是与其他REST框架不同的问题。
其他框架基本上把HTTP看作是一种远程过程调用的形式。新信息只在请求对应的响应中到来——异步函数的返回值。
Siesta在“REST”中“重新加入”了“ST”,即在架构原则中拥抱了“状态转移”的概念,将观察状态的行为与转移状态的行为分离。
如果这种做法听起来吸引人,那么就试用一下Siesta吧。
此仓库包含一个简单的示例项目。要下载示例项目,安装其依赖项,并在本地运行它。
pod try Siesta
(注意不需要先本地下载/克隆 Siesta;此命令会为您做到这一点。)要报告错误,请提交问题。
要指出文档中的错误或混淆之处,请提交问题。
要提交功能请求/好主意,请提交问题。
要寻求帮助,请将您的问题发布到Stack Overflow,并使用标签 siesta-swift。 (请务必包括标签。它会触发通知。)
想要做点事情,而不仅仅是讨论?太棒了!大胆行动。
master
分支中的 Docs
目录生成的,因此您应该在您修改位置进行更改。不要针对 gh-pages
提交拉取请求。