为什么
如今每个应用程序都与一些后端连接,通常这是通过网络层实现的,通常是一个单例,负责接受输入,执行网络请求,解析响应并返回结果。
在复杂项目中,这种方法可能会导致网络层成为一个包含超过20,000行代码的庞大且难以维护的文件。是的,这是一个真实的故事。
Nomosi 的理念是将网络层分成不同的 服务,其中每个服务代表一个远程资源。
每个服务都是独立和原子的,这使得基于模块的应用程序开发、客户端 API 版本控制、大型团队协作、测试和维护代码库变得更加容易。
特点
声明式函数式语法
Nomosi 的核心对象是一个 Service,声明为 Service<Response: ServiceResponse>
,即一个泛型类,其中占位符 Response
符合 ServiceResponse
协议。
这样,您就不会有一个处理大量请求的单例,而是会有不同的 服务,并且对于每个服务可以立即明白期望得到的内容。
设置必要的属性(url、method 等)后,通过调用 load()
函数执行新的请求。还可能以优雅的函数式方式链式调用多个动作,如 onSuccess
、onFailure
、addingObserver
。
示例
/**
The service class: a resource "blueprint", here it is possible to set endpoint, cache policy, log level etc...
*/
class AService<AServiceResponse>: Service<Response> {
init() {
super.init()
basePath = "https://api.a-backend.com/v1/resources/1234"
cachePolicy = .inRam(timeout: 60*5)
log = .minimal
decorateRequest { [weak self] completion in
// here you can decorate the request as you wish,
// for example you can place here the token refresh logic
// it is possible to pass a ServiceError to the completion block or nil
completion(nil)
}
}
}
/**
The service response, since it conforms `Decodable`, there's no need to implement the parse function.
*/
struct AServiceResponse: Decodable {
var aPropertyOne: String?
var aPropertyTwo: String?
}
// callback-based approach
AService()
.load()
.onSuccess { response in
// response is an instance of `AServiceResponse`: Type-safe swift superpower!
}
.onFailure { error in
// handle error
}
}
// async/await-based approach
do {
let response = try await AService().load()
// response is an instance of `AServiceResponse`: Type-safe swift superpower!
print(response)
} catch {
print(error)
}
设计时类型安全
利用 Swift 的类型系统和最新特性,使用 Nomosi,您永远不会直接处理 JSON 和混合数据内容。您可以忘记像 Marshal
和 SwiftyJSON
这样的第三方库。
易于装饰(例如:令牌刷新)和/或使请求无效
处理令牌和请求验证可能会很棘手。这就是为什么引入了 decorateRequest
闭包。
在启动网络任务之前,将调用该闭包。然后,使用完成块,可以取消或装饰即将执行的网络请求。
示例
class TokenProtectedService<ServiceResponse>: Service<Response> {
init() {
super.init()
basePath = "https://api.aBackend.com/v1/resources/1234"
decorateRequest { [weak self] completion in
AuthManager.shared.retrieveToken { token in
if let token = token {
self?.headers["Authorization"] = token
completion(nil)
} else {
completion(ServiceError(code: 100, reason: "Unable to retrieve the token"))
}
}
}
}
}
简单直观的缓存配置,选择您需要的层(默认为`URLCache`)
缓存通过CacheProvider
协议处理。
默认的URLCache
支持此协议,使用podspec Nomosi/CoreDataCache
可以将CoreData
用作持久存储。
如果您想使用其他持久层库(如Realm
、CouchBase
等...),您只需实现三个方法
func removeExpiredCachedResponses()
func loadIfNeeded(request: URLRequest,
cachePolicy: CachePolicy,
completion: ((_ data: Data?) -> Void))
func storeIfNeeded(request: URLRequest,
response: URLResponse,
data: Data,
cachePolicy: CachePolicy,
completion: ((_ success: Bool) -> Void))
丢弃无效或冗余请求
Nomosi确保每个执行的请求都是有效且唯一的。
例如,如果在同一服务上连续两次调用load()
方法,只会执行一个请求,第二次调用将收到一个冗余请求错误。
模拟支持
模拟通过定义如下MockProvider
协议处理:
protocol MockProvider {
var isMockEnabled: Bool { get }
var mockedData: DataConvertible? { get }
var mockBundle: Bundle? { get }
}
默认情况下,通过搜索名为ServiceName.mock
的bundle中的文件来检索模拟。
示例
// UserService.swift
class UserService<User>: Service<Response> {
init(userID: Int) {
super.init()
basePath = "https://api.aBackend.com/v1/users/\(userID)"
}
}
// User.swift
struct User {
let name: String
let surname: String
let website: String
}
// UserService.mock
{
"name": "Mario",
"surname": "Iannotta",
"website": "http://www.marioiannotta.com"
}
开发和附件第三方组件
任何符合ServiceObserver
协议的类都可以在请求开始和结束时被通知;所有UI组件,如加载器和精美的按钮,都是通过此协议构建的。
预配置UI组件
安装Nomosi/UI
后,您可以使用不同的预配置组件,例如
NetworkActivityIndicatorHandler
用于处理状态栏中的网络活动指示器。RemoteImageService
用于在imageview中使用自定义加载器和占位符高效地加载、缓存和显示远程图像。ServiceOverlayView
在执行请求时处理加载器,并在旁边显示任何发生的错误和重试按钮。ServiceObserverButton
在执行请求时对按钮执行自定义动画(大小调整、显示加载器、隐藏内容等...)。
关于所有这些是如何工作的,您可以查看服务流程图。
安装
Cocoapods
pod 'Nomosi'
Carthage
github "MarioIannotta/Nomosi"
许可
Nomosi 可在 MIT 许可下使用。有关更多信息,请参阅 LICENSE 文件。
待办事项
- 记录所有公开的内容
- 支持 async/await(即 Swift 并发)
- 添加单元测试
- 添加 SSL 指针支持
- CoreData 缓存提供者
- 下载请求
- 上传请求
- 添加模拟服务的方式
- 提供通用的接口以便可以通过仅实现 loadIfNeeded 和 storeIfNeeded 方法来使用任何存储层
- UIImageView.Placeholder 在单元格重用时不一定工作正常
- 添加状态栏活动指示器
- 将 pod 拆分为 podspec (Core + UI)
- 将字典作为正文提供
- HTTP 状态码范围验证