Hierarchies: Finding Parents, Children and Descendents using Swift

This is an old post!

This post is over 2 years old. Solutions referenced in this article may no longer be valid. Please consider this when utilizing any information referenced here.

It usually doesn’t take beginning macOS/iOS developers long to discover NotificationCenter and see it as the solution to every single problem of passing data around to different controllers. And NotificationCenter is great, but it has some downsides. Notably, it is very easy to introduce retain cycles (and memory leaks) unless you are very careful to track and free the listener when the object is released. This has bitten me on several occasions.

In general, excessive use of NotificationCenter ends up creating a difficult to maintain app where it is not entirely clear what is responding to what and where.

But the thing is, a lot times you don’t need NotificationCenter. You really only need it when you need to pass data to views or controllers that are not in the same hierarchy as your current one.

That’s right. Just like my previous posts about using the responder chain, most controllers and views in Cocoa and CocoaTouch already exist in hierarchies. And those hierarchies can be traversed and data passed to children directly without NotificationCenter.

Calling Descendents Directly

Let’s say you have a NSViewController subclass, and you want to notify all descendents that adhere to a certain protocol that something changed.

Start by defining a Protocol. Swift is big on Protocols.

protocol DoesSomething {
    public func doSomething()
}

Now, we define a second protocol, and use protocol extensions to basically create a mixin that we can use anywhere.

import Foundation
import Cocoa

protocol FindsChildren {
    func findChild<T>(type: T.Type) -> T?;
    func findChild<T>(type: T.Type, parent: NSViewController) -> T?;
    func findChild<T>(type: T.Type, parent: NSView) -> T?;
    func findAllChidrenOf<T>(type: T.Type, parent: NSViewController) -> [T];
    func findAllChidrenOf<T>(type: T.Type, parent: NSView) -> [T];
}

extension FindsChildren {
    func findChild<T>(type: T.Type) -> T? {
        if self is NSView {
            return findChild(type: type, parent: self as! NSView)
        } else if self is NSViewController {
            return findChild(type: type, parent: self as! NSViewController)
        }

        return nil
    }

    func findChild<T>(type: T.Type, parent: NSViewController) -> T? {
        for child in parent.children {
            if child is T {
                return child as? T
            } else if let c = findChild(type: type, parent: child) {
                return c
            }
        }

        return nil
    }

    func findChild<T>(type: T.Type, parent: NSView) -> T? {
        for child in parent.subviews {
            if child is T {
                return child as? T
            } else if let c = findChild(type: type, parent: child) {
                return c
            }
        }

        return nil
    }

    func findAllChidrenOf<T>(type: T.Type, parent: NSViewController) -> [T] {
        var r: [T] = []
        for child in parent.children {
            if child is T {
                r.append(child as! T)
            } else {
                let c = findAllChidrenOf(type: type, parent: child)
                if c.count > 0 {
                    r.append(contentsOf: c)
                }
            }
        }

        return r
    }

    func findAllChidrenOf<T>(type: T.Type, parent: NSView) -> [T] {
        var r: [T] = []
        for child in parent.subviews {
            if child is T {
                r.append(child as! T)
            } else {
                let c = findAllChidrenOf(type: type, parent: child)
                if c.count > 0 {
                    r.append(contentsOf: c)
                }
            }
        }

        return r
    }
}

The above code is for macOS, but converting it to iOS is as easy as changing all references to NSView to UIView.

So now, our controller can inherit FindsChildren, giving us access to the find methods we created above. At this point, we simply call findAllChildrenOf() with the protocol we created above, and call the method on them. Because we’re using generic types in Swift, the find methods return the type that was passed in as an argument.

class ViewController: NSViewController, FindsChildren {
    override public func viewDidLoad() {
        let children = findAllChildrenOf(type: DoesSomething, parent: self)
        for child in children {
            child.doSomething()
        }
    }
}

Because the methods in the FindsChildren are recursive, they will find all children that descend from self, no matter how deep in the hierarchy they may be. And, like always, you can either use protocols to find any child that conforms to the protocol, or you can specify a specific class to be called.

Calling Parents Directly

Calling parents directly is even more straightforward, because parents are automatically in the responder chain. So the method in my previous post about using the responder chain will work here as well.

Comments (0)

Interested in why you can't leave comments on my blog? Read the article about why comments are uniquely terrible and need to die. If you are still interested in commenting on this article, feel free to reach out to me directly and/or share it on social media.

Contact Me
Share It
Swift
Object oriented programming is great, but sometimes things don’t fit neatly into a superclass/subclass hierarchy. You may have a piece of code that would be needed in several contexts, but for technical reasons beyond your control you cannot merge them into a single hierarchy. Some languages have the concept of multiple inheritence, where a subclass can specifically inherit from several parents. But this has it’s own set of problems. Many other languages, however, solve this through the use of traits or mixins. These allow us to have a set of methods that are basically copied into the object at compile time. This way they can be used anywhere they are needed. Swift doesn’t have the concept of mixins or traits per se. But, starting with Swift 3, you can get very equivalent functionality using protocol default implementations.
Read More
Swift
Somewhat related to my previous post about responder chains, sometimes it is useful to be able to debug what all is in the responder chain at any given time. As a good rule of thumb, all ancestor views of a view are in that view’s responder chain, as well as (usually) the related controllers.
Read More
Swift
The responder chain is one of those parts of macOS and iOS development that may seem a little strange if you have not done any GUI programming before. Briefly, a responder chain is a hierarichy of objects that can respond to events. So, for example, a click or a tap might be passed up the responder chain until something responds to the action. But, the responder chain is more than just UI events. We can pass our own custom events up the responder chain as well!
Read More