Home > database >  Strongly typing a Dictionary or NSDictionary to have keys and values as per a protocol
Strongly typing a Dictionary or NSDictionary to have keys and values as per a protocol

Time:10-01

Here's my protocol:

@objc public protocol EventListenerOptions {
  @objc optional var capture: Bool { get set }
}

I have this method signature:

func addEventListener(
  _ type: NSString,
  _ callback: ((_ event: UIEvent) -> Void)?,
  _ options: EventListenerOptions?
)

How do I invoke it? I've tried using a statically declared dictionary and it's not accepting it. The suggested fix of inserting as! EventListenerOptions produces a compiler warning (and crashes at runtime, in any case).

view.addEventListener(
  "tap",
  {(event: UIEvent) -> Void in
    print("Got a tap event.")
  },
  ["capture": true] // Error: Argument type '[String : Bool]' does not conform to expected type 'EventListenerOptions'
)

Requirements: I want to expose the protocol to Obj-C, so what I'm looking for is some way to get type-safety in Swift while handling an object that is easily constructed in Obj-C (so I can't use structs, to my understanding). I was hoping I could just pass in an NSDictionary casted as EventListenerOptions, but it doesn't accept that.

CodePudding user response:

Unlike in some languages, such as TypeScript, Swift has nominal typing rather than structural typing. What this means is that even if an object has the shape you want, unless its class explicitly adopts the protocol, you can't pass it in.

protocol EventListenerOptions {
  var capture: Bool { get set }
}

class AnEventListenerOptionsType: EventListenerOptions {
  var capture: Bool
}

class NotAnEventListenerOptionsType {
  var capture: Bool
}

The type of your dictionary ["capture": true] is not a class which conforms to EventListenerOptions: it is the standard library type Dictionary<String, Bool>. Not only does this type not adopt the protocol, it doesn't even have the relevant property: you have to access it by dict["capture"] rather than dict.capture. This is an important difference: the former calls the subscript(_:) accessor, while the latter accesses the capture property. (If you are coming from TypeScript, as @Alexander suggests, you're probably used to these being equivalent, but in Swift they aren't.)

As far as I'm aware, Swift doesn't have anonymous object literals as in JS, C#, Kotlin etc.

TL;DR: the solution is to create a class which conforms to EventListenerOptions.

CodePudding user response:

try to add

class Listener: EventListenerOptions { 
 var capture: Bool = true
 }
...
let listener = Listener()
listener.capture = true
view.addEventListener(
  "tap",
  {(event: UIEvent) -> Void in
    print("Got a tap event.")
  },
 listener
)
  • Related