【问题标题】:How to use EventLoopFuture in Swift properly?如何在 Swift 中正确使用 EventLoopF​​uture?
【发布时间】:2020-03-25 21:25:02
【问题描述】:

我是 EventLoop 期货和承诺的新手。我的软件栈:

  • Go + gRPC 中的后端
  • Swift + SwiftUI + GRPC + NIO 中的 iOS 客户端

我有一些工作要做,并且正在寻找有关如何改进它的建议,因为我在 .map.flatMap.always 等周围的文档中有点迷失了。

这是我在 iOS 应用中的 gRPC 数据单例中的一个相关函数:

import Foundation
import NIO
import GRPC

class DataRepository {
    static let shared = DataRepository()
    // skip ...

    func readItem(id: Int64, eventLoop: EventLoop) -> EventLoopFuture<V1_ReadResponse> {
        // TODO: Is this the right place?
        defer {
            try? eventLoop.syncShutdownGracefully()
        }

        let promise = eventLoop.makePromise(of: V1_ReadResponse.self)

        var request = V1_ReadRequest()
        request.api = "v1"
        request.id = id

        let call = client.read(request, callOptions: callOptions) // client - GRPCClient initialized in the singleton

        call.response.whenSuccess{ response in
            return promise.succeed(response)
        }

        call.response.whenFailure{ error in
            return(promise.fail(error))
        }

        return promise.futureResult
    }

我在 SwiftUI 视图中的代码:

import SwiftUI
import NIO

struct MyView : View {
    @State private var itemTitle = "None"

    var body: some View {
        Text(itemTitle)
    }

    func getItem() {
        let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
        let result = DataRepository.shared.readItem(id: 1, eventLoop: eventLoopGroup.next())

        _ = result.always { (response: Result<V1_ReadResponse, Error>) in

            let res = try? response.get()                
            if let resExist = res {
                self.itemTitle = resExist.item.title
            }

            _ = response.mapError{ (err: Error) -> Error in
                print("[Error] Connection error or item not found: \(err)")
                return err
            }
        }
    }

我应该重构getItem 和/或readItem 吗?有什么具体建议吗?

【问题讨论】:

    标签: swift promise future grpc swift-nio


    【解决方案1】:

    我有一个非常具体的建议,然后是一些一般性的建议。第一个,最具体的建议是,如果您不编写 NIO 代码,您可能根本不需要创建 EventLoops。 grpc-swift 将创建自己的事件循环,您可以简单地使用它们进行回调管理。

    这让您可以将 readItem 代码重构为:

    func readItem(id: Int64, eventLoop: EventLoop) -> EventLoopFuture<V1_ReadResponse> {
        var request = V1_ReadRequest()
        request.api = "v1"
        request.id = id
    
        let call = client.read(request, callOptions: callOptions) // client - GRPCClient initialized in the singleton
        return call.response
    }
    

    这极大地简化了您的代码,让您基本上可以将管理事件循环的所有复杂工作都交给 grpc-swift,因此您可以担心您的应用程序代码。

    否则,这里有一些一般性建议:

    不要关闭不属于你的事件循环

    readItem 的顶部有这个代码:

    // TODO: Is this the right place?
    defer {
        try? eventLoop.syncShutdownGracefully()
    }
    

    您可以看到我在上面的示例中删除了它。那是因为这不是合适的地方。事件循环是长期存在的对象:构建和关闭它们的成本很高,因此您很少希望这样做。通常,您希望您的事件循环由相当高级的对象(如AppDelegate,或一些高级视图控制器或View)构建和拥有。然后它们将被系统中的其他组件“借用”。正如我所指出的,对于您的特定应用程序,您可能希望您的事件循环由 grpc-swift 拥有,因此您不应该关闭 任何 事件循环,但一般来说,如果您采用此策略,则适用明确的规则:如果您没有创建 EventLoop,请不要将其关闭。这不是你的,你只是借来的。

    事实上,在 NIO 2.5.0 中,NIO 团队 made it an error 以这种方式关闭事件循环:如果您将 try? 替换为 try!,您会在应用程序中看到崩溃。

    EventLoopGroups 是顶级对象

    在您的MyView.getItem 函数中,您创建一个MultiThreadedEventLoopGroup。我上面的建议是你根本不要创建自己的事件循环,但如果你要这样做,这不是一个好地方。

    对于 SwiftUI,最好的办法是让您的 EventLoopGroup 成为 EnvironmentObject,由 AppDelegate 注入到视图层次结构中。然后,每个需要 EventLoopGroup 的视图都可以安排从环境中提取一个,这样您就可以在应用程序中的所有视图之间共享事件循环。

    线程安全

    EventLoopGroups 使用他们自己的线程的私有池来执行回调和应用程序工作。在getItem 中,您可以从这些未来回调之一中修改视图状态,而不是从主队列中修改。

    在修改状态之前,您应该使用DispatchQueue.main.async { } 重新加入主队列。您可能希望将其包装在这样的辅助函数中:

    extension EventLoopFuture {
        func always<T>(queue: DispatchQueue, _ body: (Result<T>) -> Void) {
            return self.always { result in
                queue.async { body(result) }
            }
        }
    }
    

    回调重构

    作为一个次要的生活质量,这段代码可以重构:

    let res = try? response.get()                
    if let resExist = res {
        self.itemTitle = resExist.item.title
    }
    
    _ = response.mapError{ (err: Error) -> Error in
        print("[Error] Connection error or item not found: \(err)")
        return err
    }
    

    到这里:

    switch response {
    case .success(let response):
        self.itemTitle = response.item.title
    case .failure(let err):
        print("[Error] Connection error or item not found: \(err)")
    }
    

    【讨论】:

      猜你喜欢
      • 2021-04-23
      • 2021-10-30
      • 2021-11-23
      • 2020-09-24
      • 2021-03-24
      • 2020-11-11
      • 2022-10-16
      • 1970-01-01
      • 2017-03-22
      相关资源
      最近更新 更多