如何创建自定义完成处理程序?及Starscream WebSocket函数适配方案
自定义完成处理程序与Starscream WebSocket响应回调实现指南
嘿,我来帮你搞定这两个问题,都是iOS开发里和闭包、WebSocket相关的常见需求,直接上干货:
问题1:如何创建自定义完成处理程序?
自定义完成处理程序本质就是根据你的业务场景定义专属的闭包类型,不用搞太复杂,举几个常用的例子:
- 如果你只需要知道任务“完成了”,不需要返回数据,用基础无参闭包就行:
typealias BasicCompletion = () -> Void - 要是需要拿到成功结果或者错误信息,就定义带可选结果和错误的闭包:
typealias ResultCompletion<T> = (T?, Error?) -> Void - 想更清晰地区分成功/失败状态?可以用枚举封装结果,可读性更强:
enum CustomResult<T> { case success(T) case failure(Error) } typealias EnumBasedCompletion<T> = (CustomResult<T>) -> Void
定义好之后,在函数里把这个闭包作为参数,在任务完成的时机调用它就OK。比如写个模拟异步请求的例子:
func loadUserData(completion: @escaping ResultCompletion<String>) { // 模拟网络延迟 DispatchQueue.global().asyncAfter(deadline: .now() + 1.5) { let isSuccess = Bool.random() if isSuccess { completion("用户ID: 12345", nil) } else { completion(nil, NSError(domain: "LoadError", code: 404, userInfo: [NSLocalizedDescriptionKey: "用户数据加载失败"])) } } }
问题2:为Starscream的write函数创建带非空响应的自定义完成处理程序
首先得明确:Starscream原生的write(string:completion:)里的completion只告诉你“数据发出去了”,服务器的响应是通过WebSocket的代理方法(比如didReceive)回调的。所以要实现“发完消息拿到非空响应”,得把write操作和代理回调关联起来,具体步骤如下:
实现步骤
定义专属的完成处理程序类型,我们需要接收非空的响应字符串,所以定义成带可选响应的闭包(留个nil的口子处理异常情况):
typealias WebSocketResponseCompletion = (String?) -> Void用字典存待处理的请求回调,因为可能同时发多个请求,得用唯一ID把每个请求和对应的completion绑定:
private var pendingCallbacks = [UUID: WebSocketResponseCompletion]()封装自定义的write方法,给每个请求生成UUID,把completion存进字典,然后把ID和消息一起发给服务器(需要服务器配合返回这个ID):
extension WebSocket { func writeAndWaitForResponse(message: String, completion: @escaping WebSocketResponseCompletion) { guard isConnected else { completion(nil) // 未连接直接返回nil return } let requestID = UUID() pendingCallbacks[requestID] = completion // 把请求ID和消息打包成JSON发送,让服务器能识别这个请求 let requestPayload = ["requestId": requestID.uuidString, "content": message] do { let jsonData = try JSONSerialization.data(withJSONObject: requestPayload) guard let payloadString = String(data: jsonData, encoding: .utf8) else { completion(nil) pendingCallbacks.removeValue(forKey: requestID) return } // 调用原生write方法发送 self.write(string: payloadString) { // 原生completion只通知发送成功,这里暂时不需要额外操作 } } catch { completion(nil) pendingCallbacks.removeValue(forKey: requestID) } } }在代理方法里处理服务器响应,解析出请求ID,找到对应的completion并传递非空响应:
class YourWebSocketManager: WebSocketDelegate { private var webSocket: WebSocket! private var pendingCallbacks = [UUID: WebSocketResponseCompletion]() // 初始化WebSocket等代码省略... func didReceive(event: WebSocketEvent, client: WebSocket) { switch event { case .text(let responseText): // 解析服务器返回的JSON响应,假设返回格式包含requestId和responseContent guard let responseData = responseText.data(using: .utf8), let responseDict = try? JSONSerialization.jsonObject(with: responseData) as? [String: Any], let responseIdStr = responseDict["requestId"] as? String, let requestId = UUID(uuidString: responseIdStr), let completion = pendingCallbacks[requestId], let nonEmptyResponse = responseDict["responseContent"] as? String, !nonEmptyResponse.isEmpty else { // 解析失败或者响应为空,返回nil return } // 传递非空响应给completion completion(nonEmptyResponse) // 用完移除回调,避免内存泄漏 pendingCallbacks.removeValue(forKey: requestId) case .error(let error): // 发生错误时,把所有待处理的回调都触发并返回nil for (id, completion) in pendingCallbacks { completion(nil) pendingCallbacks.removeValue(forKey: id) } default: break } } }
关键注意点
- 服务器配合:这个方案要求服务器返回响应时必须带上你发送的
requestId,否则没法把响应和请求对应起来。 - 内存管理:闭包里要用
weak self(如果在类里的话),并且用完回调要从字典里移除,避免循环引用和内存泄漏。 - 异常处理:要覆盖未连接、JSON序列化失败、响应为空等情况,确保completion总能被调用,不会出现“回调丢失”的问题。
内容的提问来源于stack exchange,提问作者Silverstar
相关产品推荐
相关产品推荐

