具有关联值的枚举的优雅序列化/反序列化

时间:2015-10-28 12:51:56

标签: swift enums

我想将enum与关联值一起用于类型安全NSNotification s:

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. 任何可以减少冗长和改善的东西 可读性。
  4. 好的,这里有rawValue属性如何简化:

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

2 个答案:

答案 0 :(得分:3)

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

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的能力留给读者练习。

答案 1 :(得分:2)

我想我已经想出如何使NSNotification的处理更加优雅:

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!")
        }
    }
}

不确定在一种方法中处理所有通知都是好的设计(我想在有很多通知的情况下它可能会变得混乱),但重点是我们现在可以使用类型安全和模式匹配的强大功能,这很棒。