DEV Community

Oleksandra Sildushkina
Oleksandra Sildushkina

Posted on

Binding data between classes with closures in Swift

There are several ways of binding data between classes you can use in Swift.
Let's start from the simple situation. We've got a parent and a child. If parent wan't to get child grades, it can just refer to them. But what if we've got a parent that wants to know a new mark as soon as child gets it?

class Parent {
    var name: String
    var child: Child?

    init(_ name: String, child: Child? = nil) {
        self.name = name
        self.child = child
    }

    func getChildMarks() -> [Int]? {
        return child?.marks
    }
}

class Child {
    var name: String
    var marks: [Int]

    init(_ name: String, marks: [Int] = [], parent: Parent? = nil) {
        self.name = name
        self.marks = marks
    }
}

let child = Child("David", marks: [5, 5, 4])
let parent = Parent("Ms. Red", child: child)

print(parent.getChildMarks())

We should bind data. Let’s imagine, that our child is a responsible one and it has nothing against informing parents himself. But a child does not hold a reference to a parent object. So the most straightforward way is to give a child a reference to its parent. Let’s do that.

class Child {
    var name: String
    var marks: [Int]
    weak var parent: Parent?

    init(_ name: String, marks: [Int] = [], parent: Parent? = nil) {
        self.name = name
        self.marks = marks
    }
}

let child = Child("David", marks: [5, 5, 4])
let parent = Parent("Ms. Red", child: child)
child.parent = parent

Now children holds a reference to a parent. Please pay attention that reference to a parent class should be weak. That will prevent a reference cycle if parent object will be deallocated. Now we can create a public method of a parent, which a child will call each time it gets a new mark.

    func childGotNewMark(_ mark: Int) {
        print("Child got \(mark).")
    }

And each time a child gets a new mark, it adds it to the mark list and calls parents method to inform a parent.

    func getMark(_ mark: Int) {
        marks.append(mark)
        parent?.childGotNewMark(mark)
    }

This way has several disadvantages. What if we will attach a child to a parent that doesn’t have a childGotNewMark method? Obviously, we will get an error.

error: value of type ‘Parent’ has no member ‘childGotNewMark’

What can we do to resolve this issue?
The most common way is providing parent methods to a child as variables. Optional variables, so parent can either set them or not. So this child will be able to work with different parents. Let’s take a look at this solution. You can run this code in playground. Detailed explanations are going next.

class Parent {
    var name: String
    var child: Child?

    init(_ name: String, child: Child? = nil) {
        self.name = name
        self.child = child
        guard let child = child else {
            return
        }
        child.onNewMarkReceived = { [weak self] mark in
            self?.childGotNewMark(mark)
        }
    }

    func getChildMarks() -> [Int]? {
        return child?.marks
    }

    func childGotNewMark(_ mark: Int) {
        print("Child got \(mark).")
    }
}

class Child {
    var name: String
    var marks: [Int]
    weak var parent: Parent?
    var onNewMarkReceived: ((Int) -> Void)?

    init(_ name: String, marks: [Int] = [], parent: Parent? = nil) {
        self.name = name
        self.marks = marks
    }

    func getMark(_ mark: Int) {
        marks.append(mark)
        onNewMarkReceived?(mark)
    }
}

let child = Child("David", marks: [5, 5, 4])
let parent = Parent("Ms. Red", child: child)
child.parent = parent

child.getMark(5)

First we define a variable in child’s class.

var onNewMarkReceived: ((Int) -> Void)?

What does that mean? We created a variable onNewMarkReceived that is an optional method. It gets an integer on input and returns void.
Next we will call this method when child gets it’s mark instead of parent?.childGotNewMark(mark)

onNewMarkReceived?(mark)

Question mark after the name ot the method means, that if the method in our variable is not defined, it will do nothing.
On the next step we set the variable in parent’s initialisation code. It is a closure that takes mark (as an integer value) and doesn’t return anything.

init(_ name: String, child: Child? = nil) {
        self.name = name
        self.child = child
        guard let child = child else {
            return
        }
        child.onNewMarkReceived = { mark in
            print("Child got \(mark)")
        }
    }

But what if we want to pass this mark to some method of the parent instead of printing it? Let’s call func childGotNewMark(_ mark: Int) we’ve created previously.

child.onNewMarkReceived = { [weak self] mark in
            self?.childGotNewMark(mark)
        }

What do we do here? First, we set our self as weak. That means that reference to a parent object is weak. So if parent object will be going to deallocate it’s memory, it will be able to do it, not worrying about the reference that closure holds to it.
Next we refer to a method inside a parent. Self is required in a closure. Question mark after self is needed as at the moment when closure is called self can be already deallocated.
So we’ve created a data binding from a child class to parent without using delegate pattern. It allows us to work with different parents, that might refrain of defining onNewMarkReceived method. It is also tolerant for the situations when parent is deallocated.

Top comments (0)