CodyFire 1.15.4

CodyFire 1.15.4

Mihael Isaev 维护。



 
依赖项
Starscream~> 3.0.2
Alamofire~> 4.7.3
 

CodyFire 1.15.4

  • 作者:
  • MihaelIsaev

Mihael Isaev

MIT License Swift 4.2 Swift.Stream


这个库是用来进行网络请求,带有扑克牌、轮盘赌和骰子的刺激体验!

使用它,您可以将大量的 API 层代码转化为极易使用的控制器,进行简单且可维护的网络请求!

使用 Codable 模型处理所有与 API 请求相关的事务

  • json 有效载荷
  • url 编码有效载荷
  • multipart 有效载荷
  • 纯文本和 json 响应
  • url 查询
  • 头部信息

这只是您从这个库中可以获得的一小部分!🍻

如何发送多个请求?

您可以依次运行多达10个请求!

API.employee.all()
    .and(API.office.all())
    .and(API.car.all())
    .and(API.event.all())
    .and(API.post.all())
    .onError { error in
        print(error.description)
    }.onSuccess { employees, offices, cars, events, posts in
   // do what you want with received results!!! 🍻
}

或者,如果您只需要完成处理程序,您可以逐个或同时运行无限数量的请求。

[API.employee.all(), API.office.all(), API.car.all()].flatten().onError {
    print(error.description)
}.onSuccess {
    print("flatten finished!")
}

要并发运行它们,只需添加 .concurrent(by: 3) 以同时运行3个

当然,您还可以发送PUT和PATCH请求,上传带有进度回调的多部分可编码结构,捕获错误,甚至为每个端点重新定义错误描述。有什么想法吗?😃让我们阅读下面的整个readme!🍻

如何安装

CodyFire通过CocoaPods和SPM提供。

要安装,只需在您的Podfile中添加以下行

pod 'CodyFire', '~> 1.15.0'

或者您在寻找反应式代码支持?我可以做!🍺

pod 'RxCodyFire', '~> 1.1.0'
# no need to install `CodyFire` cause it's in dependencies

使用此pod,您应该始终导入RxCodyFire,并且每个APIRequest都将有.observable

pod 'ReactiveCodyFire', '~> 1.1.0'
# no need to install `CodyFire` cause it's in dependencies

使用此pod,您应该始终导入ReactiveCodyFire,并且每个APIRequest都将有.signalProducer

如何设置

CodyFire会自动检测您处于什么环境,所以我建议您尽量使用这个酷炫的功能👏

import CodyFire

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        let dev = CodyFireEnvironment(baseURL: "https://:8080")
        let testFlight = CodyFireEnvironment(baseURL: "https://stage.myapi.com")
        let appStore = CodyFireEnvironment(baseURL: "https://api.myapi.com")
        CodyFire.shared.configureEnvironments(dev: dev, testFlight: testFlight, appStore: appStore)
        //Also if you want to be able to switch environments manually just uncomment the line below (read more about that)
        //CodyFire.shared.setupEnvByProjectScheme()
        return true
    }
}

cool 吧?😏

或者为每个APIRequest设置不同的服务器URL

let server1 = ServerURL(base: "https://server1.com", path: "v1")
let server2 = ServerURL(base: "https://server2.com", path: "v1")
let server3 = ServerURL(base: "https://server3.com")

然后初始化您的APIRequest如下🔥

APIRequest(server1, "endpoint", payload: payloadObject)
APIRequest(server2, "endpoint", payload: payloadObject)
APIRequest(server3, "endpoint", payload: payloadObject)

在某些情况下,您甚至可以这样做😏

APIRequest("endpoint", payload: payloadObject).serverURL(server1)

创建您的API控制器

我保证,这是您梦想成真的API代码架构!

创建一个 API 文件夹并在其中创建一个 API.swift 文件

class API {
    typealias auth = AuthController
    typealias task = TaskController
}

API 文件夹内创建一个名为 Controllers 的文件夹,并为每个控制器创建一个文件夹

API/Controllers/Auth/Auth.swift

class AuthController {}

API/Controllers/Task/Task.swift

class TaskController {}

为每个控制器的端点创建一个扩展文件

Auth登录作为简单的POST请求

API/Controllers/Auth/Auth+Login.swift

import CodyFire
extension AuthController {
  struct LoginRequest: JSONPayload {
        let email, password: String
        init (email: String, password: String) {
            self.email = email
            self.password = password
        }
    }

    struct LoginResponse: Codable {
        var token: String
    }

    static func login(_ request: LoginRequest) -> APIRequest<LoginResponse> {
        return APIRequest("login", payload: request).method(.post).addCustomError(.notFound, "User not found")
    }
}
Auth登录为基本认证

API/Controllers/Auth/Auth+Login.swift

import CodyFire
extension AuthController {
    struct LoginResponse: Codable {
        var token: String
    }

    static func login(email: String, password: String) -> APIRequest<LoginResponse> {
        return APIRequest("login").method(.post).basicAuth(email: email, password: password)
            .addCustomError(.notFound, "User not found")
    }
}
Task REST端点
通过ID获取任务或通过偏移和限制获取任务列表

API/Controllers/Task/Task+Get.swift

import CodyFire
extension TaskController {
    struct Task: Codable {
        var id: UUID
        var name: String
    }

    struct ListQuery: Codable {
        var offset, limit: Int
        init (offset: Int, limit: Int) {
            self.offset = offset
            self.limit = limit
        }
    }

    static func get(_ query: ListQuery? = nil) -> APIRequest<[Task]> {
        return APIRequest("task").query(query)
    }

    static func get(id: UUID) -> APIRequest<Task> {
        return APIRequest("task/" + id.uuidString)
    }
}
创建任务

API/Controllers/Task/Task+Create.swift

import CodyFire
extension TaskController {
    struct CreateRequest: JSONPayload {
        var name: String
        init (name: String) {
            self.name = name
        }
    }

    static func create(_ request: CreateRequest) -> APIRequest<Task> {
        return APIRequest("post", payload: request).method(.post).desiredStatusCode(.created)
    }
}
编辑任务

API/Controllers/Task/Task+Edit.swift

import CodyFire
extension TaskController {
    struct EditRequest: JSONPayload {
        var name: String
        init (name: String) {
            self.name = name
        }
    }

    static func create(id: UUID, request: EditRequest) -> APIRequest<Task> {
        return APIRequest("post/" + id.uuidString, payload: request).method(.patch)
    }
}
删除任务

API/Controllers/Task/Task+Delete.swift

import CodyFire
extension TaskController {
    static func delete(id: UUID) -> APIRequest<Nothing> {
        return APIRequest("post/" + id.uuidString).method(.delete).desiredStatusCode(.noContent)
    }
}

轻松使用您的API端点!

发送登录请求
API.auth.login(email: "[email protected]", password: "qwerty").onError { error in
    switch error.code {
    case .notFound: print("User not found")
    default: print(error.description)
    }
}.onSuccess { token in
    print("Received auth token: "+ token)
}
获取任务列表
API.task.get().onError { error in
    print(error.description)
}.onSuccess { tasks in
    print("received \(tasks.count) tasks")
}
创建一个任务
API.task.create(TaskController.CreateRequest(name: "Install CodyFire")).onError { error in
    print(error.description)
}.onSuccess { task in
    print("just created new task: \(task)")
}
删除一个任务
let taskId = UUID()
API.task.delete(id: taskId).onError { error in
    print(error.description)
}.onSuccess { _ in
    print("just removed task with id: \(taskId)")
}

多部分示例

//declare a PostController
class PostController()
extension PostController {
    struct CreateRequest: MultipartPayload {
        var text: String
        var tags: [String]
        var images: [Attachment]
        var video: Data
        init (text: String, tags: [String], images: [Attachment], video: Data) {
            self.text = text
            self.tags = tags
            self.images = images
            self.video = video
        }
    }

    struct PostResponse: Codable {
        let id: UUID
        let text: String
        let tags: [String]
        let linksToImages: [String]
        let linkToVideo: String
    }

    static func create(_ request: CreateRequest) -> APIRequest<PostResponse> {
        return APIRequest("post", payload: request).method(.post)
    }
}

//then somewhere send creation request!

let videoData = FileManager.default.contents(atPath: "/path/to/video.mp4")!
let imageAttachment = Attachment(data: UIImage(named: "cat")!.jpeg(.high)!,
                                 fileName: "cat.jpg",
                                 mimeType: .jpg)
let payload = PostController.CreateRequest(text: "CodyFire is awesome",
                                           tags: ["codyfire", "awesome"],
                                           images: [imageAttachment],
                                           video: videoData)
API.post.create(payload).onProgress { progress in
    print("tracking post uploading progress: \(progress)")
}.onError { error in
    print(error.description)
}.onSuccess { createdPost in
    print("just created post: \(createdPost)")
}

简单吧?🎉

详细信息

如何将 "Authorization Bearer" 令牌放入每一次请求中?

为此,我们有一个全局头部封装器,它在每次请求时都会调用。

您需要声明它,例如在AppDelegate中。

有两种选择

  1. 使用可编码的模型进行头部 (推荐)
CodyFire.shared.fillCodableHeaders = {
    struct Headers: Codable {
        var Authorization: String? //NOTE: nil values will be excluded
        var anythingElse: String
    }
    return Headers(Authorization: nil, anythingElse: "hello")
}
  1. 使用 [String: String] 字典
CodyFire.shared.fillHeaders = {
    guard let apiToken = LocalAuthStorage.savedToken else { return [:] }
    return ["Authorization": "Bearer \(apiToken)"]
}

如何设置全局的 未授权 处理程序?

同样,在AppDelegate中声明它,如下所示:CodyFire.shared.unauthorizedHandler = { //踢出用户 }

.onNetworkUnavailable {
    print("unfortunately there're no internet connection!")
}

在请求开始前运行某些操作(仅在网络可用时生效)

.onRequestStarted {
    print("request started normally")
}

如何避免请求导致的日志错误

.avoidLogError()

如何设置期望的状态码以及它意味着什么?

通常服务器会以 200 OK 响应,CodyFire 默认期望接收到 200 OK 来调用 onSuccess 处理器。

您可能需要指定另一个状态码,例如,对于某些 POST 请求,为 201 CREATED

.desiredStatusCode(.created)

或者您甚至可以设置自己的自定义状态码

.desiredStatusCode(.custom(777))

如何为请求设置某些头部信息?

.headers(["myHeader":"myValue"])
//or for basic auth
.basicAuth(email: "", password: "")

支持哪些 HTTP 方法

您可以使用:GET、POST、PUT、PATCH、DELETE、HEAD、TRACE、CONNECT、OPTIONS

如何通过Xcode的运行方案切换环境?

这是一个非常实用的功能,我建议在每个iOS项目中都使用它!

创建三个方案,命名为:Development、TestFlight、AppStore,如下截图所示2018-10-24 5 30 30

提示:确保它们被标记为Shared以便它们可以在git中使用。

然后在每个方案的Arguments标签页中添加名为env环境变量,并选择以下其中一个值:dev、testFlight、appStore。

查看下面的示例2018-10-24 5 34 43

然后在AppDelegate的didFinishLaunchingWithOptions方法中添加以下内容:

CodyFire.shared.setupEnvByProjectScheme()

完成啦,现在你可以轻松地切换环境了!

如何在没有onSuccess clojure的情况下执行请求?

这对于DELETE或PATCH请求有时很有用

APIRequest<Nothing>("endpoint").method(.delete).execute()

如何取消请求?

let request = APIRequest("").execute()
request.cancel()

这样你就可以处理取消操作了

.onCancellation {
    print("request was cancelled :(")
}

自定义错误意味着什么?

您可以根据需要为自己定义全局或针对每个请求的自定义错误。`onError`块包含一个包含`StatusCode`枚举、错误描述和一个原始响应`Data`的`NetworkError`对象。您可以将错误描述更改为任何错误代码。默认情况下,已经定义了一些通用错误的良好描述。

让我们看看如何使用强大的`onError`块。

.onError { error in
    switch error.code {
    case .notFound: print("It's not found :(")
    case .internalServerError: print("Oooops... Something really went wrong...")
    case .custom(let code): print("My non-standard-custom error happened: " + error.description)
    case .unknown(let code): print("Totally unknown error happened: " + error.description)
    default:
        print("Another error happened: " + error.description)
        if let raw = error.raw, let rawResponse = String(data: raw, encoding: .utf8) {
            print("Raw response: " + rawResponse)
        }
    }
}

除了这些,您还可以在声明APIRequest时在自己的控制器中添加自定义错误!🙀

APIRequest("login")
    .method(.post)
    .basicAuth(email: "[email protected]", password: "qwerty")
    .addError(.notFound, "User not found")

我觉得这真的很棒且实用!最终,许多东西都可以在一个地方声明!🎉

如何设置响应超时?

.responseTimeout(30) //request timeout set for 30 seconds

当然,您也可以捕获超时

.onTimeout {
    //timeout happened :(
}

如何添加交互式额外超时?(我最喜欢的一种😄)

如果您想确保请求将耗时2秒或更长(不要太快😅)您可以这样做

.additionalTimeout(2)

例如,如果您的请求将在0.5秒内执行,则在请求完成后1.5秒将触发onSuccess处理器,但如果请求耗时超过2秒,则将立即触发onSuccess处理器

如何声明multipart请求的数据负载模型

您的结构体/类只需遵守MultipartPayload协议即可

struct SomePayload: MultipartPayload {
    let name: String
    let names: [String]
    let date: Date
    let dates: [Dates]
    let number: Double
    let numbers: [Int]
    let attachment: Data
    let attachments: [Data]
    let fileAttachment: Attachment
    let fileAttachments: [Attachment]
}

支持的负载类型

您可以将结构体/类遵守以下之一:FormURLEncodedPayloadMultipartPayloadJSONPayload

如何声明JSON请求的数据负载模型

您的结构体/类只需遵守JSONPayload协议即可

struct SomePayload: JSONPayload {
    let name: String
    let names: [String]
    let date: Date
    let dates: [Dates]
    let number: Double
    let numbers: [Int]
}

如何为x-www-form-urlencoded请求声明payload模型

您的struct/class只需遵守FormURLEncodedPayload协议

struct SomePayload: FormURLEncodedPayload {
    let name: String
    let names: [String]
    let date: Date
    let dates: [Dates]
    let number: Double
    let numbers: [Int]
}

如何声明url查询参数模型

您的struct/class只需遵守Codable协议

struct SomePayload: Codable {
    let name: String
    let names: [String]
    let date: Date
    let dates: [Dates]
    let number: Double
    let numbers: [Int]
}

如何设置日期解码/编码策略

我们支持的DateCodingStrategy

  • secondsSince1970
  • millisecondsSince1970
  • formatted(customDateFormatter: DateFormatter) 默认所有日期都使用yyyy-MM-dd'T'HH:mm:ss'Z'格式

在这里您有几种有趣的选项

  • 您可以设置全局日期解码器/编码器
CodyFire.shared.dateEncodingStrategy = .secondsSince1970
let customDateFormatter = DateFormatter()
CodyFire.shared.dateDecodingStrategy = .formatted(customDateFormatter)
  • 您可以在您的控制器中为请求数据设置日期解码器/编码器
APIRequest().dateDecodingStrategy(.millisecondsSince1970).dateEncodingStrategy(.secondsSince1970)
  • 或者为每种payload类型使用不同的日期编码器/解码器(优先级最高)
struct SomePayload: JSONPayload, CustomDateEncodingStrategy, CustomDateDecodingStrategy {
   var dateEncodingStrategy: DateCodingStrategy
   var dateDecodingStrategy: DateCodingStrategy
}

如何启用/禁用日志记录

例如,在AppDelegate中,您可以设置日志模式

CodyFire.shared.logLevel = .debug
CodyFire.shared.logLevel = .error
CodyFire.shared.logLevel = .info
CodyFire.shared.logLevel = .off

您还可以设置日志处理器

CodyFire.shared.logHandler = { level, text in
    print("manually printing codyfire error: " + text)
}

默认情况下,对于AppStore的日志级别为.off

如何检测当前环境?

这很容易

#if DEBUG
    //DEV environment
#else
    if Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt" {
        //TESTFLIGHT environment
    } else {
        //APPSTORE environment
    }
#endif

链式请求

逐个运行多达10个请求!

API.employee.all()
    .and(API.office.all())
    .and(API.car.all())
    .and(API.event.all())
    .and(API.post.all())
    .onError { error in
        print(error.description)
    }.onSuccess { employees, offices, cars, events, posts in
   // do what you want with received results!!! 🍻
}

onRequestStarted, onNetworkUnavailable, onCancellation, onNotAuthorized, onTimeout也都有提供! //待定: onProgress

我认为这很棒!特别是对于那些不熟悉或不习惯于响应式编程的人🙂

扁平化

如果你想逐个运行多个请求或在同一时间运行但只有一个完成处理程序,可以使用.flatten()

[API.employee.all(), API.office.all(), API.car.all()].flatten().onError {
    print(error.description)
}.onSuccess {
    print("flatten finished!")
}

要同时运行,只需添加.concurrent(by: 3),同时运行3个以提高效率。为了跳过错误,请添加.avoidCancelOnError(),要获取进度请添加.onProgress

贡献

请随时提交拉取请求并在问题中提问

希望这个库在你的项目中真的很实用!告诉你的朋友!请按下STAR⭐️按钮!!!

作者

Mike Isaev, [email protected]