extension MyBlog: UnderstandingDelegate {

Table of Content

Moar Delegates

In my previous blog post, I mentioned how I didn’t have a perfect metaphor to help with understanding protocols (and delegates, as a subtype of protocols). That hasn’t changed, but I do think I came up with a rather decent method to help others understand them a little better. It still has a slight reliance on understanding classes and… How about I just show you.

Where to start…

Let’s begin with everyone’s favorite use of a delegate: UITableView. Or at least something kind of like a table view. Maybe it’s worth explaining that I intend to emulate some of what goes on inside a table view that makes the delegate pattern a good fit. So if we extrapolate the concept of what a table view does, we basically end up with a class that needs some way of getting information:

/**
Since I don't know everything, I will need to find out some things from another thing.
*/
class IDontKnowEverything {

}

Now as stated, this class needs to know some info, but it doesn’t know what it needs or even where it can get it from. Maybe the source of the information is the view controller you house it in. Maybe it’s a model controller. Maybe it’s a subclassed UIButton that you stored a lot of information on (please don’t do that, but you have that power if you decide to be evil).

Ultimately, we can’t just give this class a property of just any old type to get information from, since it’s going to call specific methods to GET information on whatever source we provide. Read that again – it’s going to call specific methods to GET information on whatever source we provide. This is where protocols come in:

protocol IDontKnowEverythingDelegate: AnyObject {

    // tableView-similar related functions
    func ignorantClassWantsACountOfStrings(_ ignorantClass: IDontKnowEverything) -> Int
    func ignorantClass(_ ignorantClass: IDontKnowEverything, needsStringAtIndex index: Int) -> String
}

Since protocols can be attached to any class (and sometimes even structs, but not in this case – AnyObject means that only classes can conform to this protocol), we can design code in our custom classes to feed these newly defined methods. However, before we do that, we should give our ignorant class a property so that it can access its delegate:

class IDontKnowEverything {
    weak var delegate: IDontKnowEverythingDelegate?
}

Since the delegate is optional, the class can continue to function without a delegate, but it’ll likely be rather useless since that’s how it gets information it needs to display.

The data source

Let’s jump over from our fableView to whatever class we designate as having data. Since it’s common (albeit, not always the best design…) we’ll just use a View Controller as our master class.

class ViewController: UIViewController {
}

extension ViewController: IDontKnowEverythingDelegate {
    func ignorantClassWantsACountOfStrings(_ ignorantClass: IDontKnowEverything) -> Int {

    }
    func ignorantClass(_ ignorantClass: IDontKnowEverything, needsStringAtIndex index: Int) -> String {

    }
}

In case it’s not obvious, I just want to highlight the bit IDontKnowEverythingDelegate directly matches the protocol we defined earlier, which also means the the two functions here are what were defined in the protocol. So in the act of making our custom class have these functions and the : IDontKnowEverythingDelegate, we are conforming to the protocol.

Since ViewController is what we are designating as where to get the data, we should give it some data to provide, and then plug that data into the functions:

class ViewController: UIViewController {
    let fableViewData = ["what I did on my summer vacation", "the first day on my vacation", "I woke up", "Then, I went downtown", "to get a job"] // an elephant's worth of data
}

//Fill in the things it doesn't know!
extension ViewController: IDontKnowEverythingDelegate {
    func ignorantClassWantsACountOfStrings(_ ignorantClass: IDontKnowEverything) -> Int {
        return fableViewData.count
    }
    func ignorantClass(_ ignorantClass: IDontKnowEverything, needsStringAtIndex index: Int) -> String {
        return fableViewData[index]
    }
}

The last thing we need to do in the ViewController for this scenario is to create our ignorant fableView and hook up to the delegate property of the aforementioned fableView.

class ViewController: UIViewController {
    let fableView = IDontKnowEverything()

    let fableViewData = ["what I did on my summer vacation", "the first day on my vacation", "I woke up", "Then, I went downtown", "to get a job"] // an elephant's worth of data

    override func viewDidLoad() {
        super.viewDidLoad()
        fableView.delegate = self
    }
}

This new code now initializes a new IDontKnowEverything class as an instance named fableView, then when our view finishes loading, it sets itself as the delegate of the fableView. This works because we conformed and plugged in the data earlier.

Before we move on, another analogy of what we just did was kind of like inheriting from two different classes. ViewController directly inherits everything that UIViewController implements automatically, but also allows us to further customize and implement additional details. Similarly, we inherit from the IDontKnowEverythingDelegate protocol and while we don’t get the inherent functionality, other classes can now KNOW that the ViewController can provide everything that the IDontKnowEverythingDelegate protocol provides. And since it can now provide our fableView with data…

Explain it to me like I’m 10

Back in the IDontKnowEverything class, we are ready and amped up to show some data! But we still don’t know what to show, we just know that we want to do it! #ambition

The first thing a table view, er fableView would want to know is how many rows of data it needs to account for. So let’s pretend to display some data:

class IDontKnowEverything {

    weak var delegate: IDontKnowEverythingDelegate?

    func pretendToDisplayData() {
        let count = delegate?.ignorantClassWantsACountOfStrings(self) ?? 0
    }

We know that IDontKnowEverythingDelegate has a function that will give us an Int value counting the number of rows we should show, so the logical thing would be to call that method. So we do.

Now, since table views aren’t actually magic behind the scenes, we need to figure out, of the rows we have available, which ones are actually displayed. This is really out of the scope of this article, and besides, we are faking everything, so let’s fake this too.

class IDontKnowEverything {

    weak var delegate: IDontKnowEverythingDelegate?

...

    private func getRangeToShow() -> ClosedRange<Int> {
        // super complex algorithm to determine which table cells are currently visible in the display
        return 0...20 //jk faked
    }

Now we know what cells can be shown on the table, but what if the provided data has fewer bits of data than that? A little more juggling needs to be done to figure out which cells are displayed, and I made an approximation of how that sort of process is done, but I make no promises as to its accuracy to real life:

class IDontKnowEverything {

    weak var delegate: IDontKnowEverythingDelegate?

    func pretendToDisplayData() {
        let count = delegate?.ignorantClassWantsACountOfStrings(self) ?? 0

        let visibleRange = getRangeToShow()
        let upperBound = min(visibleRange.upperBound, count)
        let lowerBound = min(visibleRange.lowerBound, upperBound)
        let visibleRangeOfValidData = lowerBound..<upperBound
    }

...

}

We should end up with a valid range that is a subset of both what’s visible and allowing for our future data to be more or less than what is visible on screen.

The final piece to this puzzle is to actually show the cells on screen, calling the function that provides data to fill the cells. The problem is that we didn’t make a real table view, we made a fableView, so there’s no actual visual component to our mockery of good code. As a poor substitute, I will just create an array of strings, which you can then use your imagination to pretend they are in an amazingly beautiful table view. (alternatively, you could just use the real UITableView, now that you hopefully understand the delegation process better)

Anyways, here’s the frosting on our poop cake:

class IDontKnowEverything {

    weak var delegate: IDontKnowEverythingDelegate?

    func pretendToDisplayData() {
        let count = delegate?.ignorantClassWantsACountOfStrings(self) ?? 0

        ... range stuff

        var mahDatas = [String]()
        for index in visibleRangeOfValidData {
            let newString = delegate?.ignorantClass(self, needsStringAtIndex: index) ?? ""
            print(newString) // our 'cell' design is so much nicer than Apple's!
        }
    }
}

And this will print our beatiful fableView to the console!

Explain it to me like I’m 5

The power of this resides in the fact that table views don’t just straight up ask for an array of data or cells. That’s for a reason. Every initialization of a cell costs cpu and memory, and if we created a brand new one for each bit of data we show in a table view, it wouldn’t take too long for resources to get bogged down and have our devices run super slow. Instead, in a classic "fake-it-till-you-make-it" move, the table view itself manages the creating of new cells and recycles them. That’s right – it only makes roughly as many cells as will fit on screen, then when one exits the screen far enough, it gets pulled right back and put at the other end of the screen to reenter, just with new data. One goes off the top, very soon it will be back on the pretending to be another cell.

It’s a really cool illusion that feels like we are scrolling through a loooong list of data, but in truth only the data is long – the cells just move at super speed when they are off screen!

tableViewCellAnimation

If you’d like to get a copy of this example as a somewhat complete project, you can click here. As an added bonus, I also wrote another example in the project demonstrating how a networking manager could use delegation as well.

Please note that I am not claiming to know all the details of how a UITableView works internally, only the high level basics. All examples here are not drawn from anything other than inference, so there will be inaccuracies in the details. The underlying concepts, though, I stand behind.

Leave a Reply

Your email address will not be published. Required fields are marked *