0

Not sure if this is the best approach but I want to have functions accessible by an index and would prefer not to use a dictionary. So I was thinking of putting them into an array.

var array = [ func1, func2, ... ]

It looks like this is possible since functions are first class citizens.

BUt I'm wondering if you can do this on classes. That is, pass a function from a class instance to an array, without losing performance with extra closures.

class Foo {
  var array: [Function]

  init() {
    array = [ f1, f2 ]
  }

  func f1() {
    return array.length
  }

  func f2(a: Int, b: Int) {
    // ... the functions are all different.
  }
}

Wondering if anything like that is possible.

Lance
  • 75,200
  • 93
  • 289
  • 503
  • Possibly helpful: https://stackoverflow.com/q/55038194/1187415. – Martin R Mar 15 '19 at 19:36
  • Do all the functions have the same signature? Your first `func f1()` seems to return a value. – Martin R Mar 15 '19 at 19:44
  • No sorry, they will all be different, I will update. – Lance Mar 15 '19 at 19:46
  • 1
    Then `f1` and `f2` have different types. You cannot store them in a common array (only as `Any` which makes things ugly). – Martin R Mar 15 '19 at 19:49
  • Maybe it would be helpful to know how to do it with Any anyway :) – Lance Mar 15 '19 at 20:07
  • 1
    What are you trying to achieve? With `var array: [Any]` you would have to cast each element back to the correct function type in order to call the function, i.e. you have to *know* the signature of every element (or try all possible signatures). – Martin R Mar 15 '19 at 20:20
  • "That is, pass a function from a class instance to an array, without losing performance with extra closures." Nope. What you're trying to do, by definition, requires a closure, which captures the `self` parameter. You can capture an unbound instance method, but then you have to manually call it passing `self` to obtain a bound instance method. – Alexander Mar 15 '19 at 20:35
  • @Alexander wondering if you can demo how to do that. – Lance Mar 15 '19 at 20:39
  • @LancePollard I don't have time right now, but check out my explanation on the bottom of https://softwareengineering.stackexchange.com/a/348086/109689 – Alexander Mar 15 '19 at 20:46
  • When using function array, it’s much better to use same types of functions. Thus you can call foo.array[0](1,2) if change func f1 to func f1(_ I : int, _ f : Int){}. The array will have a fixed type not any! – E.Coms Mar 15 '19 at 22:15

1 Answers1

2

Swift Arrays are homogeneous, but you can use Any to box any type at the expense of losing the type information, so yes you can stick all of your functions into a single array even though they have different types. I don't particularly think its a good idea, but it does in fact work:

import UIKit
import PlaygroundSupport


class Foo {
    var array: [Any] = []

    init() {
        array = [ f1, f2 ]
    }

    func f1() -> Int {
        return array.count
    }

    func f2(a: Int, b: Int) {
        // ... the functions are all different.
    }
}

maybe this is slightly less horrible if you use the Objective C run time, since its dynamic and you can use actually use perform selector with arguments to invoke the selectors, but I still don't recommend it:

import UIKit
import PlaygroundSupport


class Foo {
    var array: [Selector] = []

    init() {
        array = [#selector(f1), #selector(f2) ]
    }

    @objc func f1() -> Int {
        return array.count
    }

    @objc func f2(a: Int, b: Int) {
        // ... the functions are all different.
    }
}

EDIT:

Option 3, enum with associated values (come up with more meaningful names than I have here):

import UIKit
import PlaygroundSupport


class Foo {

    enum Function {
        case void_Int(() -> Int)
        case intInt_Void((Int,Int) -> Void)
    }

    var array: [Function] = []

    init() {
        array = [.void_Int(f1), .intInt_Void(f2) ]
    }

    @objc func f1() -> Int {
        return array.count
    }

    @objc func f2(a: Int, b: Int) {
        // ... the functions are all different.
    }
}
Josh Homann
  • 15,933
  • 3
  • 30
  • 33
  • This definitely answers the question, but yeah I can see it not being ideal. I want to dynamically invoke a function given an integer, so an array was the first choice. Wondering what you would recommend instead. A switch statement would work, but that adds a _lot_ of processing given 100's of functions. A dictionary might work but that uses the hash under the hood. Maybe there are other approaches you know about. – Lance Mar 15 '19 at 20:40
  • If you truly don't need any type, but only finitely many types, use an enum with associated values for each possible type. This lets you put all of the functions into one array WITHOUT losing the string typing (ie the functions are boxed by the enum instead of by Any). – Josh Homann Mar 15 '19 at 20:45
  • Maybe you wouldn't mind demoing that, I'm going to try [learning about enums](https://appventure.me/2015/10/17/advanced-practical-enum-examples/). It sounds like you're saying an enum with the keys/value pairs being functions, and they will be indexed. – Lance Mar 15 '19 at 20:48