向服务器发出请求时,有时需要数据作为结果(例如GET),有时则不需要(例如PUT).在我的例子中,我有一个WebSocket连接,它用于告诉服务器事情(不返回数据),但也用于请求数据(返回的数据).

我已经为服务器定义了一个带有不同类型的请求(事件)的枚举:

public enum OutgoingEvent: String, Codable {
    case user
    case identification
    // ... more events
    
    var objectType: Codable.Type? {
        switch(self) {
        case .user:
            User.self
        // ... more cases
        default:
            nil
        }
    }
}

objectType表示预期作为请求结果的对象类型(用于解码接收到的JSON).对于一些事件,这是一些可解码的,对于其他事件,这是空的(没有预期的数据作为返回).

How do I write a function that I can call for all kinds of events, and get as a return either the kind of object specified by 100, or nothing at all (depending on the event)?此外,由于socketIO库的缘故,我需要从基于回调的函数调用转换为Async/AWait语法(但这本身与泛型部分不同).

我试过这个:

func sendWithAck<T: Codable>(event: OutgoingEvent, data: [[String : Any]], completion: @escaping (Result<T?, Error>) -> ()) throws {
            // getting data from socket ...
            guard let resultType = event.objectType else {
                completion(.success(nil))
                return
            }
            
            guard let decodedObject = try? self.decodeFromDict(object: result, type: resultType) as? T else {
                completion(.failure(ServerCommunicationError.invalidDataFromServer("Could not decode result data.")))
                return
            }
            
            completion(.success(decodedObject))
}

func sendWithAck<T: Codable>(event: OutgoingEvent, data: [[String : Any]]) async throws -> T? {
        return try await withCheckedThrowingContinuation { continuation in
            do {
                try sendWithAck(event: event, data: data) { (result: Result<T?, ServerCommunicationError>) in
                    continuation.resume(with: result)
                }
            } catch(let error) {
                continuation.resume(throwing: error)
            }
        }
    }

但当你打电话的时候:

let User : User = try await ServerService.shared.sendWithAck(event: event, data: [updateData])

我得到"Generic parameter 'T' could not be inferred""Cannot convert value of type 'T?' to specified type 'User'"

推荐答案

您可以使用protocol来代替枚举,每个枚举大小写都是符合该协议的不同类型.然后,您可以将输出类型添加为关联类型.

protocol OutgoingEvent {
    associatedtype Output: Codable
    
    // other propertie can go here too...
}

struct UserEvent: OutgoingEvent {
    typealias Output = User
    
    // implement the other properties (if any) here
}

struct SomeOtherEvent: OutgoingEvent {
    typealias Output = Never
}

// these extensions allow you to use the same syntax as enum cases, 
// ".user", ".someOther" etc.
extension OutgoingEvent where Self == UserEvent {
    static var user: UserEvent { UserEvent() }
}

extension OutgoingEvent where Self == SomeOtherEvent {
    static var someOther: SomeOtherEvent { SomeOtherEvent() }
}

通常情况下,Void表示"无输出",但Void不是Codable,所以我用Never代替.您还可以创建自己的类似Void的类型(只有一个有效值的类型):

enum Unit {
    case unit
}

然后,可以改为使用<Event: OutgoingEvent>类型参数重写sendWithAck.代码中的T现在是Event.Output:

func sendWithAck<Event: OutgoingEvent>(event: Event, data: [[String : Any]], completion: @escaping (Result<Event.Output?, Error>) -> ()) throws {
    guard Event.Output.self != Never.self else {
        completion(.success(nil))
        return
    }
    
    guard let decodedObject = try? self.decodeFromDict(object: result, type: Event.Output.self) as? Event.Output else {
        completion(.failure(ServerCommunicationError.invalidDataFromServer("Could not decode result data.")))
        return
    }
    
    completion(.success(decodedObject))
}

如果您使用自己的Unit类型,闭包只能接受Result<Event.Output, Error>(没有可选选项),因为您只需将Unit.unit传递给完成处理程序.

func sendWithAck<Event: OutgoingEvent>(event: Event, data: [[String : Any]], completion: @escaping (Result<Event.Output, Error>) -> ()) throws {
    guard Event.Output.self != Unit.self else {
        completion(.success(Unit.unit as! Event.Output))
        return
    }

用途:

try sendWithAck(event: .user, data: []) { result in
    ...
}

Ios相关问答推荐

实例方法wait在异步上下文中不可用;请改用TaskGroup;这是Swift 6中的错误''

带有动画层的UIView表示不会在工作表中设置动画

自定义油漆圆角三角形

如何将Safari调试器附加到Turbo-iOS原生应用中的模拟器

在flutter中使用flatter_screenutil这样的软件包的目的是什么?

比较 `某个协议` 实例时出现编译错误

为什么这个 SwiftUI 状态在作为非绑定参数传递时没有更新?

使用 SwiftUI 显示多个 VNRecognizedObjectObservation 边界框时偏移量错误

SwiftUI 图像与下拉视图切换一起 skip

如何判断 `NSManagedObject` 是否已被删除?

动画 UILabel 字体大小更改

iOS - 确保在主线程上执行

如何用 Swift 圆化 UILabel 的边缘

iOS 应用程序中的手动语言 Select (iPhone 和 iPad)

文件是为存档而构建的,它不是被链接的体系 struct (i386)

比较没有时间分量的 NSDates

AFNetworking 2.0 向 GET 请求添加标头

Flutter:如何创建一个新项目

Xcode 5 和 iOS 7:架构和有效架构

你如何以编程方式从视图控制器中画一条线?