You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 08:38:49