1

I am trying to create a utility to add closures as event handlers for UIControls though i am having a couple of issues, here is the code:

public func addCallBack(callback: Void -> Void, forControlEvents events: UIControlEvents)
{   
    var optClosureDict = objc_getAssociatedObject(self, &UIControlClosureDictKey) as? Dictionary<UInt, Set<ClosureWrapper>>

    if optClosureDict == nil {
        optClosureDict = Dictionary<UInt, Set<ClosureWrapper>>()
    }

    var closureDict = optClosureDict!

    if closureDict[events.rawValue] == nil {
        closureDict[events.rawValue] = Set<ClosureWrapper>()
    }

    var closures = closureDict[events.rawValue]!

    let wrapper = ClosureWrapper(callback:callback)
    addTarget(wrapper, action:"invoked", forControlEvents: events)
    closures.insert(wrapper)

    objc_setAssociatedObject(self, &UIControlClosureDictKey, optClosureDict!, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    var res = objc_getAssociatedObject(self, &UIControlClosureDictKey) as? Dictionary<UInt, Set<ClosureWrapper>>
}

1) Both of the if statements seem really ugly where i call an API that returns an optional and if it is nil i want to overwrite it with some default, then i have to force unwrap it. Is there a more idiomatic 'Swifter' way of doing this?

2) Also when i create a closureDict that has one key value pair of int to a closure, then i store it using setAssociatedObject, what i then retrieve back in getAssociatedObject i get back a dictionary with 0 key value pairs. What is going on? This is a problem because my closureWrapper isnt being retained and the whole thing isnt working

Luke De Feo
  • 2,025
  • 3
  • 22
  • 40

1 Answers1

1

You can simplify the lines:

var optClosureDict = objc_getAssociatedObject(self, &UIControlClosureDictKey) as? Dictionary<UInt, Set<ClosureWrapper>>

if optClosureDict == nil {
    optClosureDict = Dictionary<UInt, Set<ClosureWrapper>>()
}

var closureDict = optClosureDict!

to

var closureDict = objc_getAssociatedObject(self, &UIControlClosureDictKey) as? Dictionary<UInt, Set<ClosureWrapper>> ?? Dictionary<UInt, Set<ClosureWrapper>>()

Or, you can define a type to make that more readable:

typealias ClosureDict = [UInt : Set<ClosureWrapper>]

And then that line becomes:

var closureDict = objc_getAssociatedObject(self, &UIControlClosureDictKey) as? ClosureDict ?? ClosureDict()

--

Also, the following code does not do what you intended:

var closures = closureDict[events.rawValue]!

let wrapper = ClosureWrapper(callback:callback)
addTarget(wrapper, action:"invoked", forControlEvents: events)
closures.insert(wrapper)

The problem is that Set is a struct, which, unlike a class, is not a "reference" type. So the var closures = ... line is making a new copy of the Set<ClosureWrapper>, not referencing the original one. Thus, you are updating a copy of the Set<ClosureWrapper>, rather than referencing the one in the closureDict.

You could update the closureDict when you're done:

var closures = closureDict[events.rawValue]!

let wrapper = ClosureWrapper(callback:callback)
addTarget(wrapper, action:"invoked", forControlEvents: events)
closures.insert(wrapper)

closureDict[events.rawValue] = closures

Or more simply, update it in situ:

let wrapper = ClosureWrapper(callback:callback)
addTarget(wrapper, action:"invoked", forControlEvents: events)
closureDict[events.rawValue]!.insert(wrapper)

--

So this yields the following alternatives:

func addCallBack(callback: Void -> Void, forControlEvents events: UIControlEvents) {
    var closureDict = objc_getAssociatedObject(self, &UIControlClosureDictKey) as? ClosureDict ?? ClosureDict()

    if closureDict[events.rawValue] == nil {
        closureDict[events.rawValue] = Set<ClosureWrapper>()
    }

    let wrapper = ClosureWrapper(callback:callback)
    addTarget(wrapper, action:"invoked", forControlEvents: events)
    closureDict[events.rawValue]!.insert(wrapper)

    objc_setAssociatedObject(self, &UIControlClosureDictKey, closureDict, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}

or

func addCallBack(callback: Void -> Void, forControlEvents events: UIControlEvents) {
    var closureDict = objc_getAssociatedObject(self, &UIControlClosureDictKey) as? ClosureDict ?? ClosureDict()

    var closures = closureDict[events.rawValue] ?? Set<ClosureWrapper>()

    let wrapper = ClosureWrapper(callback:callback)
    addTarget(wrapper, action:"invoked", forControlEvents: events)
    closures.insert(wrapper)

    closureDict[events.rawValue] = closures

    objc_setAssociatedObject(self, &UIControlClosureDictKey, closureDict, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044