1

Consider the following code:

class Test {

    func func1(arg1: Int) -> Void {
        println(arg1)
    }

    var funcArr: Array< (Int) -> Void > = [func1] // (!) 'Int' is not a subtype of 'Test'
}

I'm trying to store the method func1in an array, but as you can see, this doesn't seem to work because func1supposedly only takes an argument of type Test. I assume this has something to do with methods needing to be associated with an object.

For some more clarification, have a look at the following code where I let swift infer the type of the array:

class Test {

    func func1(arg1: Int) -> Void {
        println(arg1)
    }

    var funcArr = [func1]
}

Test().funcArr[0](Test()) // Compiles, but nothing gets printed.
Test().funcArr[0](1) // (!) Type 'Test' does not conform to protocol 'IntegerLiteralConvertible'
Test().func1(1) // Prints '1'

A possible workaround for this problem is moving func1outside of the class like so:

func func1(arg1: Int) -> Void {
    println(arg1)
}

class Test {
    var funcArr = [func1]
}

Test().funcArr[0](1) // Prints '1'

This works fine for this simple example, but is less than ideal when I actually need to operate on an Object of type Test in the function. I can of course add another parameter to the function to pass an instance of Testto the function, but this seems clunky.

Is there any way I can store methods in an array?

Ultimately, what I want to be able to do is testObject.funcArr[someInt](someParam) and have that function work as a method belonging to testObject. Any clever workarounds are of course also welcome.

overactor
  • 1,759
  • 2
  • 15
  • 23

2 Answers2

4

Instance methods in swift are just curried functions, and the first argument is implicitly an instance of the class (i.e. self). And that's why these two are equivalent:

Test().func1(0)
Test.func1(Test())(0)

So when you try to put that function in the array, you're reveling its real nature: the method func1 on Test is actually this class method:

class func1(self_: Test)(arg1: Int)

So when you refer to simply func1 (without an "instance context") it has type Test -> Int -> Void, instead of the expected Int -> Void, and that's why you get the error

Int is not a subtype of Test

So the real issue is that when you store the methods in funcArr the instance is not known yet (or if you will, you're referring the function at a class level). You can work around this using a computed property:

var funcArr: [Int -> Void] { return [func1] } // use a computed property

Or another valuable option could be simply to acknowledge the real type of func1 and explicitly passing the instance. E.g.

...
var funcArr = [func1]
...

let test = Test()
let func1 = test.funcArr[0]
func1(test)(0) // prints 0

update

Freely inspired by this other Q/A (Make self weak in methods in Swift) I came up with a similar solution that stores the method references.

func weakRef<T: AnyObject, A, B>(weakSelf: T, method: T -> A -> B) -> A -> B {
    return { [unowned weakSelf] in { a in method(weakSelf)(a) } }()
}

class Test {
    var methodRefs: [Int -> Void] = []

    init() {
        methodRefs.append(weakRef(self, Test.func1))
    }

    func func1(arg1: Int) {
        println(arg1)
    }
}
Community
  • 1
  • 1
Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
  • I went for using your second option but I'm hesitant to accept this answer. Especially since something like [this](http://stackoverflow.com/q/25613783/3684713) seems to work, but I can't seem to make the reference to self weak like is done there. I also wonder if your first solution doesn't introduce a memory leak. – overactor Nov 13 '14 at 11:00
  • @overactor The first solution should not introduce a memory leak, since the array containing the references to the instance methods is not held by the instance itself, but just returned to the caller. I'll update my answer with a few more considerations – Gabriele Petronella Nov 13 '14 at 11:57
  • 1
    @overactor check it out. – Gabriele Petronella Nov 13 '14 at 13:18
2

In order to store a method, you should remember that the method is invoked on a class instance. What's actually stored in the array is a curried function:

(Test) -> (Int) -> Void

The first function takes a class instance and returns another function, the actual (Int) -> Void method, which is then invoked on that instance.

To make it more explicit, the array type is:

var funcArr: [(Test) -> (Int) -> Void] = [Test.func1]

Then you can invoke the method with code like this:

var test = Test()
var method = test.funcArr[0]
method(test)(1)

Suggested reading: Curried Functions

Antonio
  • 71,651
  • 11
  • 148
  • 165