ws

⚠ 重要提示:告别 ws... 你好 Networking!

Networkingws 项目的下一代版本。可以将其视为专为 iOS13 构建的 ws 2.0。它使用 Apple 的原生 Combine 框架,而不是 Then Promise 库,移除了 Arrow 依赖项,从而倾向于 Codable(但 Arrow 仍然可以轻松适配),并移除了 Alamofire 依赖项,从而倾向于更简单的纯原生 URLSession 实现。本质上,减少了依赖项,增加了原生内容,同时具有几乎相同的 API。如果你的应用支持 iOS13 及更高版本,强烈建议迁移到 Networking。WS 将出于向后兼容性原因进行“维护”,但从 iOS13 开始,请将其视为已弃用。

ws

Language: Swift 5 Platform: iOS 8+ Carthage compatible Cocoapods compatible License: MIT Build Status codebeat badge Release version

原因 - 示例 - 安装

let ws = WS("http://jsonplaceholder.typicode.com")

ws.get("/users").then { json in
    // Get back some json \o/
}

因为 JSON API 在 99% 的 iOS 应用程序中使用,所以它应该 简单
我们开发人员应该 专注于我们的应用程序逻辑,而不是 样板代码
更少的代码意味着更好的代码

试试看!

ws 是 freshOS iOS 工具集的一部分。 在示例应用程序中尝试一下! 下载入门项目

如何实现

通过提供一个轻量级客户端,**自动化每个人都必须编写的样板代码**。
通过公开一个 非常简单 的 API,以简单、清晰、快速的方式完成工作。
从 JSON API 获取 Swift 模型现在已成为过去的问题

内容

用法

裸 JSON

import ws // Import ws at the top of your file
import Arrow // Import Arrow to get access to the JSON type

class ViewController: UIViewController {

    // Set webservice base URL
    let ws = WS("http://jsonplaceholder.typicode.com")

    override func viewDidLoad() {
        super.viewDidLoad()

       // Get back some json instantly \o/
       ws.get("/users").then { (json:JSON) in
           print(json)
       }
    }
}

设置模型解析

创建一个 User+JSON.swift 文件并将 JSON 键映射到你的模型属性

import Arrow

extension User: ArrowParsable {

    mutating func deserialize(_ json: JSON) {
        identifier <-- json["id"]
        username <-- json["username"]
        email <-- json["email"]
    }
}

注意:ws 使用 Arrow 进行 JSON 解析 https://github.com/freshOS/Arrow

选择你想要返回的内容

在这里,你将创建一个包装你的请求的函数。根据你想要返回的内容,有不同的编写该函数的方法。一个空块,JSON,模型或模型数组。

func voidCall() -> Promise<Void> {
    return ws.get("/users")
}

func jsonCall() -> Promise<JSON> {
    return ws.get("/users")
}

func singleModelCall() -> Promise<User> {
    return ws.get("/users/3")
}

func modelArrayCall() -> Promise<[User]> {
    return ws.get("/users")
}

你可以注意到,仅通过更改返回类型,ws 自动 知道该怎么做,例如,尝试将响应解析为 User 模型。

这使我们能够保持简洁,而无需编写额外的代码。 \o/

注意:ws 使用 then 进行 Promises 操作 https://github.com/freshOS/then

获取它!

voidCall().then {
    print("done")
}

jsonCall().then { json in
    print(json)
}

singleModelCall().then { user in
    print(user) // Strongly typed User \o/
}

modelArrayCall().then { users in
    print(users) // Strongly typed [User] \o/
}

设置

想要记录所有网络调用和响应吗?

ws.logLevels = .debug

想要隐藏网络活动指示器吗?

ws.showsNetworkActivityIndicator = false

想要覆盖默认会话管理器以自定义信任策略吗?

import Alamofire

ws.sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(
  policies: ["myspecialhostname.com" : .disableEvaluation]
))

Api 示例

这是一个典型的 Articles CRUD 示例

extension Article {

    static func list() -> Promise<[Article]> {
        return ws.get("/articles")
    }

    func save() -> Promise<Article> {
        return ws.post("/articles", params: ["name":name])
    }

    func fetch() -> Promise<Article> {
        return ws.get("/articles/\(id)")
    }

    func update() -> Promise<Void> {
        return ws.put("/articles/\(id)", params: ["name":name])
    }

    func delete() -> Promise<Void> {
        return ws.delete("/articles/\(id)")
    }

}

这是我们在代码中如何使用它的

// List Articles
Article.list().then { articles in

}

// Create Article
var newArticle = Article(name:"Cool story")
newArticle.save().then { createdArticle in

}

// Fetch Article
var existingArticle = Article(id:42)
existingArticle.fetch().then { fetchedArticle in

}

// Edit Article
existingArticle.name = "My new name"
existingArticle.update().then {

}

// Delete Article
existingArticle.delete().then {

}

HTTP 状态码

当请求失败时,我们通常想知道由于 HTTP 状态码的原因。这是如何获取它的

ws.get("/users").then {
    // Do something
}.onError { e in
    if let wsError = e as? WSError {
        print(wsError.status)
        print(wsError.status.rawValue) // RawValue for Int status
    }
}

你可以在这里找到完整的 WSError 枚举 -> https://github.com/freshOS/ws/blob/master/ws/WSError.swift

奖励 - 加载更多模式

通常,我们处理列表以及加载更多项目的功能。 在这里,我们将看到使用 ws 的此模式的示例实现。 这不包括在内,因为逻辑本身取决于你的后端实现。 这将为你提供一个示例,供你推出自己的版本。

实施

import ws
import then
import Arrow


class LoadMoreRequest<T:ArrowParsable> {

    var limit = 12

    private var params = [String:Any]()
    private var offset = 0
    private var call: WSRequest!
    private var canLoadMore = true
    private var aCallback:((_ ts: [T]) -> [T])? = nil

    init(_ aCall: WSRequest) {
        call = aCall
    }

    func resetOffset() {
        offset = 0
        canLoadMore = true
    }

    func hasMoreItemsToload() -> Bool {
        return canLoadMore
    }

    func fetchNext() -> Promise<[T]> {
        params = call.params
        params["limit"] = limit
        params["offset"] = offset
        call.params = params
        offset += limit
        return call.fetch()
                .registerThen(parseModels)
                .resolveOnMainThread()
    }

    private func parseModels(_ json: JSON) -> [T] {
        let mapper = WSModelJSONParser<T>()
        let models = mapper.toModels(json)
        if models.count < limit {
            canLoadMore = false
        }
        return models
    }
}

正如你所看到的,我们有一个强类型的请求。
限制是可调的。
它封装了一个 WSRequest。
它处理偏移逻辑,以及是否还有更多项目要加载。

这就是我们需要的一切!

现在,这就是我们构建 LoadMoreRequest 的方式

func loadMoreUsersRequest() -> LoadMoreRequest<User> {
    return LoadMoreRequest(ws.getRequest("/users"))
}

用法

这是我们在控制器中使用它的方式

class ViewController: UIViewController {

    // Get a request
    let request = api.loadMoreUsersRequest()

    override func viewDidLoad() {
        super.viewDidLoad()
        request.limit = 5 // Set a limit if needed
    }

    func refresh() {
      // Resets the request, usually plugged with
      // the pull to refresh feature of a tableview
      request.resetOffset()
    }

    func loadMore() {
      // Get the next round of users
      request.fetchNext().then { users in
          print(users)
      }
    }

    func shouldDisplayLoadMoreSpinner() -> Bool {
      // This asks the requests if there are more items to come
      // This is useful to know if we show the "load more" spinner
      return request.hasMoreItemsToload()
    }
}

现在你有一种简单的方法来处理 App 🎉 中的加载更多请求

奖励 - 简化 restful 路由的使用

在使用 RESTFUL API 时,我们可以玩得开心并走得更远一点。

通过引入 RestResource 协议

public protocol RestResource {
    static func restName() -> String
    func restId() -> String
}

我们可以有一个构建我们的 REST URL 的函数

public func restURL<T:RestResource>(_ r:T) -> String {
    return "/\(T.restName())/\(r.restId())"
}

我们将我们的 User 模型符合协议

extension User:RestResource {
    static func restName() -> String { return "users" }
    func restId() -> String { return "\(identifier)" }
}

我们可以实现一个 get 版本,它使用我们的 RestResource

public func get<T:ArrowParsable & RestResource>(_ restResource:T, params:[String:Any] = [String:Any]()) -> Promise<T> {               
    return get(restURL(restResource), params: params)
}

然后

ws.get("/users/\(user.identifier)")

可以这样写

ws.get(user)

当然,同样的逻辑可以应用于所有其他 ws 函数 (post, put delete 等)! 🎉

安装

Swift Package Manager (SPM)

由于同时支持所有软件包管理器的挑战,SPM 支持可在单独的分支 spm-only 上使用。

Carthage

在你的 Cartfile 中

github "freshOS/ws"

/usr/local/bin/carthage copy-frameworks

添加输入文件

$(SRCROOT)/Carthage/Build/iOS/ws.framework
$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
$(SRCROOT)/Carthage/Build/iOS/Arrow.framework
$(SRCROOT)/Carthage/Build/iOS/then.framework

这链接了 ws 及其依赖项。

手动

Carthage 非常有用,因为它负责拉取依赖项,如 Arrow、then 和 Alamofire。 酷的是它确实是透明的。 我的意思是,你可以只在旁边使用 carthage 来拉取和构建依赖项,并手动将框架链接到你的 Xcode 项目。

如果没有 Carthage,我会看到 2 种解决方案:1 - 复制粘贴所有源代码:ws / then / Arrow / Alamofire,这听起来并不好玩;) 2 - 手动链接框架(ws + 依赖项),方法是 A 在每个 repo 上抓取 .frameworks 它们,或者 B 使用 Carthage 构建它们

Cocoapods

target 'MyApp'
pod 'ws'
use_frameworks!

Swift 版本

Swift 2 -> 版本 1.3.0
Swift 3 -> 版本 2.0.4
Swift 4 -> 版本 3.0.0
Swift 4.1 -> 版本 3.1.0
Swift 4.2 -> 版本 3.2.0
Swift 5.0 -> 版本 5.0.0
Swift 5.1 -> 版本 5.1.0
Swift 5.1.3 -> 版本 5.1.1

支持者

喜欢该项目吗? 提供咖啡或每月捐款支持我们,并帮助我们继续开展活动 :)

赞助商

成为赞助商,并在我们的 Github README 上展示你的徽标,并链接到你的网站 :)