iOS与WatchOS通信:如何用枚举替代字符串作字典键
解决方案
要实现直接用DeviceMessage枚举作为字典键,同时兼容WCSession要求的[String: Any]类型,你可以通过自定义字典类型+扩展的方式实现,全程避免硬编码字符串和手动调用rawValue。
1. 完善枚举定义
确保枚举遵循Hashable(String类型枚举默认已实现该协议,显式声明更清晰):
enum DeviceMessage: String, Hashable { case foo, bar // 建议遵循Swift小写开头的命名规范 }
2. 自定义消息字典类型
创建以DeviceMessage为键的字典类型,同时实现与[String: Any]的双向转换:
typealias DeviceMessageDictionary = [DeviceMessage: Any] extension DeviceMessageDictionary { // 转换为WCSession可接收的[String: Any]格式 var asStringKeyedDict: [String: Any] { reduce(into: [:]) { result, entry in result[entry.key.rawValue] = entry.value } } // 从[String: Any]反向转换为自定义字典 init?(fromStringKeyedDict dict: [String: Any]) { self.init() for (keyStr, value) in dict { guard let key = DeviceMessage(rawValue: keyStr) else { // 遇到未知键可选择忽略或返回nil终止初始化,这里选择后者 return nil } self[key] = value } } }
3. 扩展WCSession简化发送逻辑
给WCSession扩展直接接收DeviceMessageDictionary的方法,省去每次手动转换的步骤:
extension WCSession { func sendDeviceMessage(_ message: DeviceMessageDictionary, errorHandler: ((Error) -> Void)? = nil) { let stringKeyedDict = message.asStringKeyedDict if isReachable { sendMessage(stringKeyedDict, replyHandler: nil, errorHandler: errorHandler) } else { transferUserInfo(stringKeyedDict) } } }
4. 发送消息的调用方式
现在可以直接用枚举作为键构造消息,无需处理字符串:
// 构造消息 let message: DeviceMessageDictionary = [ .foo: "Hello from iOS", .bar: 12345 ] // 发送 session.sendDeviceMessage(message) { error in print(error?.localizedDescription ?? "Send failed") }
5. 接收消息的处理
在WCSession委托方法中,把收到的[String: Any]转成自定义字典,用枚举键直接取值:
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) { guard let messageDict = DeviceMessageDictionary(fromStringKeyedDict: userInfo) else { print("Received invalid message") return } DispatchQueue.main.async { // 用枚举键直接获取对应值 if let fooContent = messageDict[.foo] as? String { self.message = fooContent } else if let barContent = messageDict[.bar] as? Int { self.message = "Bar value: \(barContent)" } else { self.message = "Unknown" } } }
额外优化:统一消息结构
如果你的消息是「类型标识+内容」的固定结构,可以进一步封装成结构体,让通信逻辑更规范:
struct WCMesssage: Codable { let type: DeviceMessage let content: AnyCodable // 自定义AnyCodable处理任意类型的Codable兼容 // 转换为[String: Any] var asDictionary: [String: Any] { ["type": type.rawValue, "content": content.value] } // 从[String: Any]初始化 init?(fromDictionary dict: [String: Any]) { guard let typeRaw = dict["type"] as? String, let type = DeviceMessage(rawValue: typeRaw), let content = dict["content"] else { return nil } self.type = type self.content = AnyCodable(content) } } // 自定义AnyCodable结构体,支持任意基础类型的编码解码 struct AnyCodable: Codable { let value: Any init(_ value: Any) { self.value = value } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch value { case let v as String: try container.encode(v) case let v as Int: try container.encode(v) case let v as Double: try container.encode(v) case let v as Bool: try container.encode(v) case let v as [String: Any]: try container.encode(v.mapValues(AnyCodable.init)) case let v as [Any]: try container.encode(v.map(AnyCodable.init)) default: throw EncodingError.invalidValue(value, .init(codingPath: [], debugDescription: "Unsupported type")) } } init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let v = try? container.decode(String.self) { value = v } else if let v = try? container.decode(Int.self) { value = v } else if let v = try? container.decode(Double.self) { value = v } else if let v = try? container.decode(Bool.self) { value = v } else if let v = try? container.decode([String: AnyCodable].self) { value = v.mapValues { $0.value } } else if let v = try? container.decode([AnyCodable].self) { value = v.map { $0.value } } else { throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Unsupported type")) } } }
内容的提问来源于stack exchange,提问作者Eric Dolecki
相关产品推荐
相关产品推荐

