1

I have a situation where I use a class to do a number of conversions with a large number of rules which have the general form of:

  private class func rule0(inout account: String, _ version: Int) -> String? {
    return nil; // Use default rule.
  }

The function names just increase the rule number: rule1, rule2 etc...

I get the rule number from an external source and now want to find a generic way to call the right rule function depending on the given rule number. I could use a big switch statement, but I'm more interested in generating a selector from a string and call that.

My attempts led me to:

let ruleFunction = Selector("rule\(rule):_:");
let result = ruleFunction(&account, version);

but that only causes this error on the second line:

'(inout String, @lvalue Int) -> $T4' is not identical to 'Selector'

What is the right way to accomplish that? And btw, what means "$T4"? That error message is hard to understand, to say it friendly.

Additional note: I'm aware of approaches using a timer or detouring to Obj-C, but am rather interested in a direct and pure Swift solution. How is the Selector class to be used? Can it be used to directly call what it stands for? How do classes like NSTimer use it (detour to Obj-C?)?

Mike Lischke
  • 48,925
  • 16
  • 119
  • 181
  • You could alternatively consider an object command pattern. – Wain Dec 18 '14 at 14:22
  • This from the Apple docs: The performSelector: method and related selector-invoking methods are not imported in Swift because they are inherently unsafe. – jwlaughton Dec 18 '14 at 14:24
  • If you look around stackoverflow, you'll see some discussion about how to use timers in Swift as a workaround for the performSelector method. Also if you read the Apple Docs, you'll see that there are some uses for the Selector type in Swift; just not performSelector. – jwlaughton Dec 18 '14 at 14:33
  • I didn't specifically asked for performSelector, but that might be a way too. I thought more of a function pointer, like in C/C++, but any other approach is welcome as well. Btw: I did not find a posting where people use NSTimer as **solution** for performSelector. Rather they wanted an answer to specify a selector in an otherwise unrelated NSTimer question. – Mike Lischke Dec 18 '14 at 15:12
  • If you get the rule number from someplace else, why not then use a switch statement depending on the rule number? Of course you'd need to return rule number as an int. – jwlaughton Jan 18 '15 at 13:01
  • I thought of that, however, it's quite a large number of rules and it can happen that one rule uses another one, which I couldn't implement in a switch. – Mike Lischke Jan 18 '15 at 13:02
  • Look at this: http://stackoverflow.com/questions/24158427/alternative-to-performselector-in-swift – jwlaughton Jan 18 '15 at 13:03
  • BTW I faced a similar problem with an Objective C application. The method (function) to be called was randomly determined at run time. In Objective C I could do this with [self performSelector:NSSelectorFromString(selectorNames[selectorNumber])];. I think you really want an alternative to performSelector. – jwlaughton Jan 18 '15 at 13:25
  • Well, I don't really care if it is an alternative to performSelector. What I want is to run a function whose actual name is constructed at runtime. I know the link you gave, however, most answers don't care about the dynamic name nature I want and those that do do ugly things (create separate thread, create a timer, use an unrelated function for control, which I don't have). – Mike Lischke Jan 18 '15 at 16:17
  • do all of the "rules" have the same function signature? are they all `(String, Int) -> String?` – Aaron Rasmussen Jan 19 '15 at 22:27
  • @RomanSausarnes, yes they have all the same signature, otherwise it would be impossible to work with them that way. – Mike Lischke Jan 20 '15 at 08:05

1 Answers1

1

A couple of approaches that don't use Selector. If all of the "rules" have the same signature, then you could either put them in an array, with the index of the function in the array indicating the rule number, like this:

let functionArray = [rule0, rule1, rule2, rule3]  //  etc...

Then, when it is time to call the function, you just access the one you want with a subscript:

let result = functionArray[ruleNumber](account, version)

You could do the same thing with a dictionary that looked something like this:

let functionDict = ["rule0": rule0, "rule1": rule1]  //  etc...

And then get the function using the string value that you create when you get the rule number from your external source:

let result = functionDict["rule\(ruleNumber)"]?(account, version)

The dictionary approach would work better if you weren't able to guarantee that rule0 would always be at index 0 in the array.

A caution, though: I have tried this and I've been able to get it to work without the inout keyword, but not with it. I don't know if it is a bug, or what, but Swift doesn't seem to want to let me create arrays or dictionaries of functions that take inout parameters.

If you absolutely require the inout parameter for your function to work, then my suggestion won't help. If there is a way to do what you want without the inout, then this might be the solution for you.

Aaron Rasmussen
  • 13,082
  • 3
  • 42
  • 43
  • Roman, you went through exactly the same process as I did. Currently I have an array of closures that take the function addresses. See https://github.com/mike-lischke/IBANTools/blob/master/Source/DERules.swift ( the rules array). Also I saw the problem with inout params. They are crashing the swift compiler. – Mike Lischke Jan 20 '15 at 08:11
  • @Matt: I posted a question about the `inout` parameter issue and got [this response](http://stackoverflow.com/a/28036280/4051126). It looks like you can use `UnsafeMutablePointer` to store the functions in an array and it will work. Kind of a hack, but it might solve your problem. – Aaron Rasmussen Jan 20 '15 at 14:39
  • Ok, I assigned the bounty to you even though I did not get the answer I wanted. But you tried at least and I lose the points in any case. – Mike Lischke Jan 23 '15 at 08:08