Search code examples
swiftxcodefunctionchain

Array of functions don't keep their values from their creation


I try to manage my functions with a Manger. The problem is that the function code updates and is not saved in the moment when i add the function to my manager. I try to explain my problem with this example:

class QueueManager {

    typealias FunctionType = () -> ()

    private var functions = [(String, FunctionType)]()

    func add(funcName: String, function: FunctionType) -> QueueManager {
        functions.append(funcName, function)
        return self
    }

    func runFirst() -> Bool {
        guard functions.isEmpty == false else { return false }
        functions.first!.1()
        functions.removeFirst()
        return true
    }
}

Then i do this:

let queueManager = QueueManger()

var value = 1

queueManager.add("simpleFunction"){
    print(value)
}

value = 2

queueManager.add("simpleFunction"){
    print(value)
}

queueManager.runFist()
queueManager.runFist()

And the result is:

2 // Because value is 2 in both functions. But i added the function while value was 1??
2 

But i want the result:

1
2

What am i doing wrong? Thanks in advance!

EDIT: Very easy playground example:

import UIKit


var str = "1"
func go() {
    print(str)
}

var array:[()->()] = []
array.append(go)

str = "2"

array.append(go)
array[0]()
array[1]()

// Output:
// 2
// 2

EDIT 2: I know that 2 2 is the right output for my code. But i want to keep the function in the state of its creation. Is this somehow possible?

EDIT 3: Thanks for all of your help. But i think i`m failing to explain my problem enough to get suiting answers. I want to call a function with its parameters at a later time. I don't want to keep the reference to the parameter values. I just need to call the function with those parameter values.


Solution

  • In order to understand what happens here lets take a look step-by-step :

    1. Assign 1 to value
    2. Add the print instruction to QueueManager
    3. Assign 2 to value
    4. Add the print instruction to QueueManager
    5. Run the functions using runFirst()

    When you add the print(value) instruction you pass value as reference type. This creates a strong reference between variable functions and value. Hence when you actually execute those instructions, using runFirst() it then uses the value stored in value at that point of time.

    Let's explore using this example:

    var value = 5
    
    queueManager.add(funcName: "simpleFunction"){
        print(value)
    }
    
    queueManager.add(funcName: "simpleFunction"){
        print(value)
    }
    
    queueManager.runFirst()
    queueManager.runFirst()
    
    value = 10
    
    // output is 5  5
    

    In this case we perform runFirst() first and then update the value. Hence the output is 5 5.

    TL;DR - Pass By Reference causes function to print the current value of variable value.

    EDIT : Bind the data to the function in QueueManager, this will make sure that the current value of data (during function definition) is associated with the function.

    class QueueManager {
    
        typealias FunctionType = (Int) -> ()
        private var functions = [(String, FunctionType, Int)]()
    
        func add(funcName: String, function: @escaping FunctionType, data: Int) -> QueueManager
        {
            functions.append((funcName, function, data))
            return self
        }
    
        func runFirst() -> Bool
        {
            guard functions.isEmpty == false else { return false }
            functions.first!.1(functions.first!.2)
            functions.removeFirst()
            return true
        }
    }
    
    let queueManager = QueueManager()
    
    // define you function within this closure
    let functionClosure: (Int) -> () = { (data) in
        print(data)
    }
    
    var value = 1
    queueManager.add(funcName: "simpleFunction", function: functionClosure, data: value)
    
    value = 2
    queueManager.add(funcName: "simpleFunction", function: functionClosure, data: value)
    
    queueManager.runFirst()
    queueManager.runFirst()
    

    OUTPUT :

    1
    2