我是EventLoop期货和Promise的新手。我的软件堆栈:
由于我对.map
,.flatMap
,.always
等文档有些迷茫,因此我有一些工作要做,并且正在寻求有关改进建议的建议。< / p>
这是我在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
吗?有什么具体建议吗?
答案 0 :(得分:2)
我有一个非常具体的建议,然后是一些一般性建议。第一个最具体的建议是,如果您不编写NIO代码,则可能根本不需要创建EventLoop
。 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!
,则会看到崩溃您的应用程序。
在MyView.getItem
函数中,创建一个MultiThreadedEventLoopGroup
。上面我的建议是,您根本不要创建自己的事件循环,但是如果您要这样做,那么这样做不是一个好地方。
对于SwiftUI,最好的做法是让EventLoopGroup
是EnvironmentObject
并由AppDelegate
注入视图层次结构。然后,每个需要EventLoopGroup
的视图都可以安排从环境中提取一个视图,这使您可以在应用程序中的所有视图之间共享事件循环。
EventLoopGroup
使用它们自己的线程的私有池来执行回调和应用程序工作。在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)")
}