3

Given a function reference, is there a way in Swift to get the name of that function as a string suitable for passing to NSSelectorFromString?

I'd like to wrap NSNotificationCenter's addObserver with a version that takes a function reference instead of a selector string:

addObserver(self, function: someCallback, name: "some notification", object: nil)

But addObserver takes a String selector argument.

Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92
  • http://stackoverflow.com/a/24049111/2477632 – HamzaGhazouani Jan 26 '15 at 19:27
  • Yes, I know how to invoke `addObserver` in swift. But I want to create a new method that takes the function reference as an argument and converts it to a string. – Christopher Pickslay Jan 26 '15 at 19:51
  • "But addObserver takes a String selector argument." No. It takes a Selector argument. They are not the same thing (even if you can sometimes use a string in Swift where a Selector is expected). – matt Jan 26 '15 at 20:14
  • Point taken @matt. But all of `Selector`'s initializers take String arguments. Is there a way to create a `Selector` representing an existing function other than using a string literal? – Christopher Pickslay Jan 27 '15 at 06:37

3 Answers3

4

You're reinventing an unnecessary wheel. NSNotificationCenter already has an observation method that takes a function (what Objective-C calls a "block"):

addObserverForName:object:queue:usingBlock:

So just use that.

matt
  • 515,959
  • 87
  • 875
  • 1,141
0

Edit: I'm leaving this here for interest, but it's way too complicated (got wrapped up in how the question was asked, rather than the goal). Beyond the existing block-based approach, there's also this handy extension to it.


I wouldn't do it this way. It's too limiting because it would exclude function literals (anonymous functions).

Instead, I would play this game:

  • Create an dictionary property mapping [String: Void -> ()] (string to function)
  • When you register a new function, make up a unique, random key and store the function you're passed in your dictionary with that key.
  • Register with the selector observer_dispatcher_<key> (or whatever prefix you like).
  • Implement resolveInstanceMethod: to dynamically create any observer_dispatcher_ method you're requested. They can all point to the same IMP, something like:

(assuming this is good Swift; I haven't tried it):

 void _observer_dispatcher(self: AnyObject, _cmd: Selector) {
    let name = // strip the leading stuff off of _cmd
    if let f = self.dispatchTable[name] {
         f()
     }
 }

(This is still pretty sloppy; I'll have to think about the correct correct impl more, but this is the direction I'd go in.)

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
0

I'd still like to find an answer to my original question, but based on @matt's suggestion, I'm currently using this extension:

extension NSNotificationCenter {

    class func addObserver(function: (NSNotification!) -> (), name: String? = nil, object: AnyObject? = nil, queue: NSOperationQueue? = nil) {
        defaultCenter().addObserverForName(name, object: object, queue: nil, usingBlock: function)
    }

}

Since it implicitly uses defaultCenter() and provides defaults for object and queue, which I'd almost always pass nil for, it allows for a more succinct call:

NSNotificationCenter.addObserver(doSomething, name: "some notification")

I like that it links against the actual function (doSomething), rather than a string representation. It's not a general purpose extension, but I think it covers 90% of the cases where I register for notifications.

Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92