【问题标题】:Elegant serialization/deserialization of enum with associated values具有关联值的枚举的优雅序列化/反序列化
【发布时间】:2016-01-28 06:10:51
【问题描述】:

我想将枚举与类型安全 NSNotifications 的关联值一起使用:

enum Notification {
   case Foo(Int)
   case Bar

   var rawValue: String { 
      switch self {
          case .Foo:
              return "Foo"

          case .Bar:
              return "Bar"
      }
   }

   var asNSNotification: NSNotification {
      let userInfo = [String: AnyObject]()

      switch self {
          case let .Foo(intVal):
              userInfo["intVal": intVal]

          default:
              break
      }

      return NSNotification(name: rawValue, object: nil, userInfo: userInfo)
   }

   init?(fromNSNotification n: NSNotification) {
      switch n.name {
          case .Bar: 
              self = .Bar

          case .Foo(42): // some bogus value
              let realValue = n.userInfo?["intVal"] ?? 0
              self = .Foo(realValue)

          default:
              return nil
      }
   }
}

这应该可以,但它肯定是一段丑陋的代码。有人知道如何使它更优雅吗?


编辑:我想使用枚举的原因是让每个通知的参数类型安全。

“更优雅”是指:

  1. 简化rawValue 属性(避免使用switch
  2. 在引用具有关联值的枚举案例时避免“虚假值” 初始化器。
  3. 任何可以减少冗长并提高 可读性。

好的,下面是如何简化rawValue 属性:

var rawValue: String {
    return Mirror(reflecting: self).children.first.flatMap({ $0.label }) ?? "\(self)"
}

【问题讨论】:

    标签: swift enums


    【解决方案1】:

    在我看来,您的枚举工作太辛苦了。这应该足够了:

    enum Notification {
        case Foo(Int)
        case Bar
        func notification() -> NSNotification {
            switch self {
            case Foo(let intVal):
                return NSNotification(name: "Foo", object: nil, userInfo: ["IntVal":intVal])
            case Bar:
                return NSNotification(name: "Bar", object: nil)
            }
        }
    }
    

    添加提供非零object 的功能留给读者练习。

    【讨论】:

    • 是的,这绝对解决了序列化的问题,谢谢:) 但是反序列化(参见初始化程序)仍然很尴尬。我看到您的解决方案根本不包括反序列化。您认为收到此通知的效果如何?
    • 不确定你想在那里实现什么,抱歉。在我看来,您将通知“返回”为枚举的想法似乎太过分了。如果它很重要,嘿,在通知用户信息中包含枚举本身的副本!但这对我来说似乎很疯狂。
    • 将通知“返回”为枚举 是的,就是这样。 在通知用户信息中包含枚举本身的副本不能这样做,它不是AnyObject
    【解决方案2】:

    我想我已经想出了如何让NSNotifications 的处理更加优雅:

    enum NotificationType: String {
       case Foo
       case Bar
    }
    
    enum Notification {
       case Foo(Int)
       case Bar
    
       // the fragile part
       var type: NotificationType { 
          let name = Mirror(reflecting: self).children.first.flatMap({ $0.label }) ?? "\(self)"
          return NotificationType(rawValue: name)!
       }
    
       var asNSNotification: NSNotification {
          let name = type.rawValue
          let userInfo = [String: AnyObject]()
    
          switch self {
              case let .Foo(intVal):
                  userInfo["intVal": intVal]
    
              default:
                  break
          }
    
          return NSNotification(name: name, object: nil, userInfo: userInfo)
       }
    
       init?(fromNSNotification n: NSNotification) {
          let type = NotificationType(rawValue: n.name)!
    
          switch type {
              case .Bar: 
                  self = .Bar
    
              case .Foo:
                  let value = n.userInfo?["intVal"] ?? 0
                  self = .Foo(value)
    
              default:
                  return nil
          }
       }
    }
    

    此解决方案依赖于NotificationNotificationType 中的案例名称相同的约定。有人可能会说这样的设计很脆弱,但我认为与我们所取得的成果相比,这是一个小小的权衡。

    下面是一个处理订阅/取消订阅的小助手类:

    protocol NotificationReceiving: class {
        func didReceiveNotification(notification: Notification)
    }
    
    class NotificationReceiver {
    
        private weak var delegate: NotificationReceiving?
    
        private(set) var subscriptions = Set<NotificationType>()
    
        init(delegate: NotificationReceiving?) {
            self.delegate = delegate
        }
    
        func subscribe(notificationTypes: NotificationType...) {
            let nc = NSNotificationCenter.defaultCenter()
            let doSubscribe: NotificationType -> Void = {
                nc.addObserver(self, selector: "handleNotification:", name: $0.name, object: nil)
                self.subscriptions.insert($0)
            }
    
            notificationTypes.forEach(doSubscribe)
        }
    
        func unsubscribe(notificationTypes: NotificationType...) {
            let nc = NSNotificationCenter.defaultCenter()
    
            if notificationTypes.isEmpty {
                nc.removeObserver(self)
            } else {
                let doUnsubscribe: NotificationType -> Void = {
                    nc.removeObserver(self, name: $0.name, object: nil)
                    self.subscriptions.remove($0)
                }
    
                notificationTypes.forEach(doUnsubscribe)
            }
        }
    
        @objc private func handleNotification(notification: NSNotification) {
            if let n = Notification(fromNSNotification: notification) {
                delegate?.didReceiveNotification(n)
            }
        }
    }
    

    这是客户的样子:

    class Client: NotificationReceiving {
        private var receiver: NotificationReceiver!
    
        init() {
            receiver = NotificationReceiver(delegate: self)
            receiver.subscribe(.Foo, .Bar)
        }
    
        deinit {
            receiver.unsubscribe()
        }
    
        func didReceiveNotification(notification: Notification) {
            switch notification {
            case let .Foo(val):
                print("foo with val: \(val)")
                // this notification is one-shot:
                receiver.unsubscribe(.Foo)
    
            case .Bar:
                print("bar!")
            }
        }
    }
    

    不确定以一种方法处理所有通知是否是好的设计(我想当有很多通知时它可能会变得混乱),但关键是我们现在可以使用类型安全和模式匹配的强大功能,这太棒了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-30
      • 1970-01-01
      • 2014-08-10
      • 2019-05-31
      • 2015-07-26
      • 1970-01-01
      • 1970-01-01
      • 2013-08-03
      相关资源
      最近更新 更多