Feathers 5.3.0

Feathers 5.3.0

测试已测试
语言语言 SwiftSwift
LICENSE MIT
发布时间最后发布2017年12月
Swift 版本Swift 版本4
SPM支持 SPM

Brendan Conron 维护。



Feathers 5.3.0

  • startupthekid

FeathersSwift

build

feathers

什么是 FeathersSwift?

FeathersSwift 是一个用于与 FeathersJS 后端交互的 Cocoa 网络库。为什么你会想使用它?

  • Swift 3👍
  • 网络抽象层
  • 无缝集成任何 FeathersJS 服务
  • 支持 iOS、macOS、tvOS 和 watchOS
  • 反应式 API (ReactiveSwift)

如果你使用 FeathersJS(你应该使用),FeathersSwift 是你的不二之选。无需再处理 HTTP 请求或套接字客户端。一个简单的接口就可以统治一切,在黑暗中,统一它们💍.

入门教程

FeathersSwift 分散在多个仓库中,以确保您只添加所需的组件,不再添加其他组件。有两个 Feathers 提供者,分别是 feathers-swift-restfeathers-swift-socketio。按照它们的各自 README 上的说明安装任何一个提供者。

您安装了一个提供者之后,无论 rest 还是 socketio,都可以抓取一个与 Feathers 应用相关联的实例。

let feathersRestApp = Feathers(RestProvider(baseURL: URL(string: "https://myserver.com")))

然后抓取一个服务

let userService = feathersRestApp.service("users")

最后发送请求

service.request(.find(parameters: ["name": "Waldo"]))
.on(value: { response in
  print(response)
})
.start()

FeathersSwift 的 API 完全使用 ReactiveSwift 构建,这是一个绝佳的功能反应式库。由于 Swift 中没有承诺,我们必须找到一个提供类似用法模式的替代方案。通过这样做,我们可以避免灾难金字塔和无休止的回调嵌套,相反,我们可以提供一个简化的反应式 API。

服务

您可以使用对应于 Feathers 服务方法的六种类型的请求

public enum Method {

    case find(query: Query?)
    case get(id: String, query: Query?)
    case create(data: [String: Any], query: Query?)
    case update(id: String?, data: [String: Any], query: Query?)
    case patch(id: String?, data: [String: Any], query: Query?)
    case remove(id: String?, query: Query?)

}

使用 .update.patch.remove 时,您可以在删除实体列表时传入 nil 的 id。实体列表由您传入的查询确定。

默认情况下,FeathersSwift 将返回一个 ProviderService 的实例,它将应用程序的传输提供者在服务中包装起来。但是,您也可以注册自己的服务

feathers.use("users-local", CoreDataService())

所有自定义服务都必须符合 ServiceType。幸运的是,由于 FeathersSwift 提供的 Service 类,这使得符合变得很容易,这个类处理诸如钩子存储和必需方法的空实现等问题。

一个简单的自定义服务可能看起来像

class FileService: Service {

  public override func request(_ method: Service.Method) -> SignalProducer<Response, FeathersError> {
        let fileManager = FileManager.default
        switch method {
        case let .create(data, _):
          guard let id = data["id"] else { break }
          let fileData = NSKeyedArchiver.archiveData(withRootObject: data)
          fileManager.createFile(atPath: "\(path)/\(id)", contents: fileData, attributes: nil)
        default: break
        }
    }

}

虽然这是一个小型示例,但自定义服务可以是无限复杂的,并且在钩子过程中的任何地方都可以使用。只需调用 hookObject.app.service("my-custom-service").request(.create(data: [:], parameters: nil)) 即可。

查询

你可能注意到,与通过请求传递参数字典不同,FeathersSwift 使用一个 Query 对象。Query 类有一个简单且可组合的 API,可以表示复杂的查询,而无需与字典纠缠。它支持所有 FeathersJS 用户熟悉和喜爱的查询,例如 neor,只是以更简化和类型安全的方式进行。

要创建一个查询

let query = Query()
  .ne("age", 50)
  .limit(25)
  .skip(5)

let service = feathers.service("users")

service.request(.find(query)).start()

那些担心自己的字典格式是否正确的日子已经过去了,Query 知道如何序列化自身并为您处理这些问题。

身份验证

为了使用您的 Feathers 后端对应用程序进行身份验证

feathersRestApp.authenticate([
  "strategy": "facebook-token",
  "access_token": "ACCESS_TOKEN"
])
.start()

身份验证返回一个 JWT 有效负载,该有效负载由应用程序缓存并用于发出后续请求。目前还没有重新认证机制,但请查看即将推出的版本。

要注销,只需调用

feathersRestApp.logout().start()

实时事件

当使用套接字提供程序时,您不仅可以使用它来调用 Feathers 服务方法,还可以监听实时事件。只需使用 feathers-swift-socketio 并使用 SocketProvider 实例创建一个 feathers 应用程序,然后通过在您的服务上使用 .on 注册事件。

有四种不同的实时事件

public enum RealTimeEvent: String {

   case created
   case updated
   case patched
   case removed

}

您可以使用这些事件来进行诸如动态更新 UI、将实体保存到数据库或仅记录事件发生等操作。

let feathersSocket = Feathers(provider: SocketProvider(baseURL: URL(string: "https://myserver.com")!, configuration: []))

let userService = feathersSocket.service(path: "users")
userService.on(.created)
.observeValues { entity in
  print(entity) // Prints the object that was just created
}

当你完成后,务必调用 .off 以注销事件。否则,提供商将保留您的完成块。

userService.off(.created)

还有一个巧妙的 .once 函数,它会执行您期望的操作;您只需监听一个事件,仅一个事件。

钩子

hooks

与 FeathersJS 类似,您可以在请求发出时注册 beforeaftererror 钩子。可能的用例可能包括使用 before 钩子进行单元测试占位或简单的记录。

要创建一个钩子,创建一个符合 Hook 的对象

public protocol Hook {
    func run(with hookObject: HookObject) -> Promise<HookObject>
}

可能看起来像这样的日志所有 create 事件的钩子

struct CreateLogHook: Hook {

  func run(with hookObject: HookObject) -> Promise<HookObject> {
    var object = hookObject
    if object.method == .create {
      print("create happened")
    }
    return Promise(value: object)
  }

}

或者您可以进行更复杂的操作,例如网络呼叫

struct FetchAssociatedUserHook: Hook {
  func run(with hookObject: HookObject) -> Promise<HookObject> {
    var object = hookObject
    guard object.app.service.path == "groups" else {
      return Promise(value: hookObject)
    }
    guard case var .get(id, parameters)  = object.method else {
      return Promise(value: hookObject)
    }
    guard let userIdentifier = parameters["user_id"] as? String else {
      return Promise(error: .myCustomError("no associated user found when expected to exist"))
    }
    return object.app.service("users").request(.get(parameters: ["id": userIdentifier])).then { response in
      if case let .jsonObject(object) = response.data {
        parameters["user_id"] = object["id"]
        object.method = .get(id, parameters)
      }
      return Promise(value: object)
    }
  }
}

需要注意的是,var object = hookObject。Swift 函数参数是 let 常量,因此您必须首先复制对象才能对其进行修改。

钩子对象

钩子对象通过钩子在后续步骤中传递。该接口与 JS 紧密对应

/// Hook object that gets passed through hook functions
public struct HookObject {

    /// Represents the kind of hook.
    ///
    /// - before: Hook is run before the request is made.
    /// - after: Hook is run after the request is made.
    /// - error: Runs when there's an error.
    public enum Kind {
        case before, after, error
    }

    /// The kind of hook.
    public let type: Kind

    /// Feathers application, used to retrieve other services.
    public let app: Feathers

    /// The service this hook currently runs on.
    public let service: Service

    /// The service method.
    public var method: Service.Method

    /// Error that can be set which will stop the hook processing chain and run a special chain of error hooks.
    public var error: FeathersError?

    /// Result of a successful method call, only in after hooks.
    public var result: Response?

    public init(
        type: Kind,
        app: Feathers,
        service: Service,
        method: Service.Method) {
        self.type = type
        self.app = app
        self.service = service
        self.method = method
    }

}

所有 var 声明都是可变的,您可以在钩子中根据需要设置和修改它们,包括 .method(如果您想进行诸如替换参数或完全更改方法调用(例如,将 .get 更改为 .find)等操作)。

关于钩子对象需要注意的一些重要事项

  • 设置 error 将导致钩子处理链停止并立即运行任何错误钩子。如果在 before 钩子中发生这种情况,请求也将被跳过。
  • before 钩子中将 result 设置为某个 Response 值将跳过请求,实质上是进行占位。

钩子注册

要注册您的钩子,您首先必须创建一个 Service.Hooks 对象

let beforeHooks = Service.Hooks(all: [LogResultHook()], create: [AppendUserIdHook()]])
let afterHooks = Service.Hooks(find: [SaveInRealmHook()])
let errorHooks = Service.Hooks(all: [LogErrorHook(destination: "log.txt")])

注册钩子同样简单

let service = app.service("users")
service.before(beforeHooks)
service.after(afterHooks)
service.error(errorHooks)

重要:首先运行注册为 all 的钩子,然后运行特定服务方法的钩子。

如果您在任何时候需要检查您的钩子,也可以使用 .hooks 来实现。

let beforeHooks = service.hooks(for: .before)

贡献

有问题?开启一个问题!有想法?提交一个拉取请求!

如果你喜欢这个库,请⭐️它!

更多信息

FeathersSwift有详细的文档,所以如果你对某个功能的实现有任何疑问,请查看源代码。你还可以在Feathers的Slack团队中向我提问 @brendan。

干杯!🍻