Why Does Apple Need Delegates?

In my post Why do we need delegates, I explained one major developer use of delegates: moving data from a destination view controller back to the controller that presented it or pushed it onto the stack. This is not the only use of delegates. Apple uses it often  in APIs. In this theoretical lesson we’ll discuss what they doing under-the-hood and why. We’ll dissect the entire process, so you will understand what you are doing in your code.

The Basic Problem

Let’s start with a simple class, which if you want you can copy into a playground to try:

class SimpleClass{
    var array:[String] = ["A","B","C","D"]
    func printArray(){
        for string in array {
            if string.characters.count != 0{
                print(string)
            }else{
                print("Empty String")
            }
        }
    }
}

We use this class this way:

let simpleClass = SimpleClass()
simpleClass.printArray()

This class prints an array. When a developer writes this you have access to the methods and functions. If you wanted to re-write this to be more customizable, you could just change the source. You can assign different values to the array.
However, Apple (and sometimes developers) don’t want to you messing with their code. Apple will suspend your developer license for doing that to an API. Often Apple writes the code so you have no access to things like array at all. Suppose our code changes to this:

class SimpleClass{
    func printArray(){
        let array:[String] = ["A","B","C","D"]
        for string in array {
            if string.characters.count != 0{
                print(string)
            }else{
                print("Empty String")
            }
        }
    }
}

Here’s the problem: You want to format string and set the values in array. You just can’t since you have no access to them.

Adding a Protocol

What Apple does for these customization situations is write protocols for the class. You’ll find two different names of this protocol. For getting data, you’ll find a protocol called a data source. For changing data or reacting to events, you’ll find a delegate.

Adding a Data Source

A data source is a function that defines the data in a class. You write the function to return the data you want to use. For example, we could add the following protocol for array

protocol SimpleClassDataSource{
    func array() -> [String]
}

We could then change our code to this:

class SimpleClass{
    var dataSource:SimpleClassDataSource! = nil
    func printArray(){
        let array:[String] = dataSource.array()
        for string in array {
            if string.characters.count != 0{
                print(string)
            }else{
                print("Empty String")
            }
        }
    }
}

Line 2 creates a property dataSource which has an optional value of SimpleClassDataSource as its type. In line 4, the code calls dataSource function array to get the data for the array.

Of course, there is no data yet. That’s in the class that will use SimpleClass and adopts the SimpleClassDataSource protocol. Suppose we had this class:

class PrintingDoughnuts{
}

We could use our simple class to print the doughnuts. First, add a constant simpleClass, and an initializer to run it.

class PrintingDoughnuts{
    let simpleClass = SimpleClass()
    
    init() {
        simpleClass.printArray()
    }
}

If we try running this code, with PrintingDoughnuts() we get an error:
fatal error: unexpectedly found nil while unwrapping an Optional value

The protocol is set to nil, and thus crashes. We still need to assign the data we want. First, we adopt the protocol,

class PrintingDoughnuts:SimpleClassDataSource{
    let simpleClass = SimpleClass()
    
    init() {
        simpleClass.printArray()
    }  
}

Then we tell simpleClass to find our implementation of array inside PrintingDoughnuts, by assigning self to the data source.

class PrintingDoughnuts:SimpleClassDataSource{
    let simpleClass = SimpleClass()
    
    init() {
        simpleClass.dataSource = self
        simpleClass.printArray()
    }
}

Finally, we implement our version of array

class PrintingDoughnuts:SimpleClassDataSource{
    let simpleClass = SimpleClass()
    
    init() {
        simpleClass.dataSource = self
        simpleClass.printArray()
    }
    
    func array() -> [String] {
        return ["Boston Cream","Cruller","Old Fashioned","","Frosted"]
    }
}

I just assigned a literal array here, but I could have just as easily called an external XML File to download data, or calculated some values. I can do anything here that returns a string array. That’s the power of a data source: While the class does not change I can do anything I want with it.

If I go and run this code by PrintingDoughnuts(), I get this output:

Boston Cream
Cruller
Old Fashioned
Empty String
Frosted

You can think of data sources setting properties otherwise inaccessible by coding, because someone made it impossible to get at the property directly.

Adding Delegates

While data sources are for properties, delegates are for methods. If you want to do something because of a change in your values or with your data you find those in a delegate. The developer of SimpleClass for example might use a delegate to change string and to react to a blank string in the array. The protocol for that might look like this:

protocol SimpleClassDelegate{
    func format(string:String)->String
    func handleEmptyString()
}

The SimpleClass would use it like this:

class SimpleClass{
    var dataSource:SimpleClassDataSource! = nil
    var delegate:SimpleClassDelegate! = nil
    func printArray(){
        let array:[String] = dataSource.array()
        for string in array {
            if string.characters.count != 0{
                print(delegate.format(string: string))
            }else{
                delegate.handleEmptyString()
            }
        }
    }
}

Line two creates an optional property called delegate with type SimpleClassDelegate and sets it to nil. Lines 8 and 10 use the methods of delegate. Again we don’t know what those methods are yet. That happens in the class that adopts the protocol.

Just like the data source, we’d implement the adoption of the protocol like this:

class PrintingDoughnuts:SimpleClassDataSource, SimpleClassDelegate{
    let simpleClass = SimpleClass()
    
    init() {
        simpleClass.dataSource = self
        simpleClass.delegate = self
        simpleClass.printArray()
    }
    // data source
    func array() -> [String] {
        return ["Boston Cream","Cruller","Old Fashioned","","Frosted"]
    }
    // delegates for self
    func format(string: String) -> String {
        return "Yummy " + string + " Doughnut"
    }
    func handleEmptyString() {
        print("Waaah!! No Doughnut!")
    }
}

Like the data source, adopt the protocol in line 1. Tell delegate in line 6 that the delegate methods can be found in this class. Lines 13 through 19 are those delegate methods.

Our output changes:

Yummy Boston Cream Doughnut
Yummy Cruller Doughnut
Yummy Old Fashioned Doughnut
Waaah!! No Doughnut!
Yummy Frosted Doughnut

External Implementation of a Protocol

One more thing about line 5 and 6. We set dataSource and delegate to self. In most cases, this is what you will do, because you want to write the required methods in the same class as you use them. However, this is not always the case. If you will use the same methods for multiple classes you may make a separate class like this:

class DoughnutDelegateMethods:SimpleClassDelegate{
    // delegates
    func format(string: String) -> String {
        return "Very Yummy " + string + " Doughnut"
    }
    func handleEmptyString() {
        print("**Waaah!! No Doughnut!**")
    }
}

We’ve adopted the protocol, and implement the required methods in this separate class with a few changes to tell the difference.
We can change line 6 of PrintingDoughnuts to this:

simpleClass.delegate = DoughnutDelegateMethods

Now the delegate is reading the methods from the DoughnutDelegateMethods class:

Very Yummy Boston Cream Doughnut
Very Yummy Cruller Doughnut
Very Yummy Old Fashioned Doughnut
**Waaah!! No Doughnut!**
Very Yummy Frosted Doughnut

We can now use the same delegate method in another class for Firecakes Doughnuts, an artisan doughnut shop in Chicago:

class PrintingFirecakes:SimpleClassDataSource{
    let simpleClass = SimpleClass()
    
    init() {
        simpleClass.dataSource = self
        simpleClass.delegate = DoughnutDelegateMethods()
        simpleClass.printArray()
    }
    // data source
    func array() -> [String] {
        return ["Triple Valrhona Chocolate Cake","Maple Glazed Pineapple & Bacon","Butterscotch Praline","","Malted Milk Ball"]
    }
}

This time we only adopted the data source.We only need to adopt the delegate when writing the delegate methods. For our delegate we told the system to find it in the class DoughnutDelegateMethods. Since they are not in this class we don’t adopt them. Running the PrintingFirecakes() in a playground gets us a list of these doughnuts too:

Very Yummy Triple Valrhona Chocolate Cake Doughnut
Very Yummy Maple Glazed Pineapple & Bacon Doughnut
Very Yummy Butterscotch Praline Doughnut
**Waaah!! No Doughnut!**
Very Yummy Malted Milk Ball Doughnut

How Apple’s API’s Use This

With that look under the hood of API delegates and data sources, we can look at what you need to do for any API that has delegates. Some have required delegates, such as the UITableView, UIImagePickerController and CLCoreLocationManager classes. In Swift, UITableview's delegate and data source are so important, Apple bundled them into the Swift version of the class, while you do have to adopt them in Objective-C.

In a non working example lets look at CLCoreLocationManager. For an API delegate or data source we have three things to do

  1. Adopt the delegate
  2. Set the location of the delegate’s method implementation, usually self to say “within this class”
  3. Implement the delegate methods

Here’s a fragment of code to show what it would look like. By now you should understand what Apple is doing. All of the methods that return values from the GPS are delegate methods found in CLLocationManagerDelegate. When one of those events occurs, that delegate method gets called. If you want to do something at that event you implement that delegate method.

/*
//
// This shoud really be run on a device, not the playground
// commented out as a non working example, which is missing functional parts
 
import MapKit
// #1- adopt the delegate
class WhereAmI:NSObject,CLLocationManagerDelegate{
let myLocationManager = CLLocationManager()
    override init(){
        super.init()
 
 // #2 - set the delegate implementation to this class
        myLocationManager.delegate = self      
    }
 //#3 - Use delegate functions
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        let myLocation = locations.first
        print ("my latitude = \(myLocation?.coordinate.latitude)")
        print ("my longitude = \(myLocation?.coordinate.longitude)")
    }
}

WhereAmI()
*/

The Whole Code

This was an under the hood explanation of something developers use often. You still might want to play with the code I wrote. You can cut and paste the code below into an Apple playground, or click here to run the code in the IBM swift sandbox.

//:
//: An example of API use of delegates and data sources
//: (c)Steven Lipton (makeapppie.com) July 2016
//: See http://makeapppie.com for explanation

import UIKit
//
//: A data source protocol
//
protocol SimpleClassDataSource{
    func array() -> [String]
}
//
//: A delegate protocol
//
protocol SimpleClassDelegate{
    func format(string:String)->String
    func handleEmptyString()
}
//
//:A simple class that uses the protocols 
//
class SimpleClass{
    var dataSource:SimpleClassDataSource! = nil
    var delegate:SimpleClassDelegate! = nil
    func printArray(){
        let array:[String] = dataSource.array()
        for string in array {
            if string.characters.count != 0{
                print(delegate.format(string: string))
            }else{
                delegate.handleEmptyString()
            }
        }
    }
}
//
//: The first example class that uses SimpleClass
//
class PrintingDoughnuts:SimpleClassDataSource, SimpleClassDelegate{
    let simpleClass = SimpleClass()
    
    init() {
    //If we want to implement the protocol in this class, we use self
        simpleClass.dataSource = self
        simpleClass.delegate = self
    //If we want to implement the protocol in an external class
        //simpleClass.delegate = DoughnutDelegateMethods()
        simpleClass.printArray()
    }
    // data source implementation
    func array() -> [String] {
        return ["Boston Cream","Cruller","Old Fashioned","","Frosted"]
    }
    // delegate implementations
    func format(string: String) -> String {
        return "Yummy " + string + " Doughnut"
    }
    func handleEmptyString() {
        print("Waaah!! No Doughnut!")
    }
}
//
//: An external implementaion of the delegate methods, used
//: when sharing the implementaion between classes
//
class DoughnutDelegateMethods:SimpleClassDelegate{
    // delegates
    func format(string: String) -> String {
        return "Very Yummy " + string + " Doughnut"
    }
    func handleEmptyString() {
        print("**Waaah!! No Doughnut!**")
    }
}
//
//: An eaxmple of a second method sharing the delegate implementation
//
class PrintingFirecakes:SimpleClassDataSource{
    let simpleClass = SimpleClass()
    
    init() {
        simpleClass.dataSource = self
        simpleClass.delegate = DoughnutDelegateMethods()
        simpleClass.printArray()
    }
    // data source
    func array() -> [String] {
        return ["Triple Valrhona Chocolate Cake","Maple Glazed Pineapple & Bacon","Butterscotch Praline","","Malted Milk Ball"]
    }
}

 let doughnutList = PrintingDoughnuts()
 let fireCakeList = PrintingFirecakes()
/*
 
//
//: An example of CLLocationManager using delegates 
//: This shoud really be run on a device, not the playground
//: commented out as a non working example, which is missing functional parts
// 
import MapKit
//: #1- adopt the delegate
class WhereAmI:NSObject,CLLocationManagerDelegate{
let myLocationManager = CLLocationManager()
    override init(){
        super.init()
 
 //: #2 - set the delegate implementation to this class
        myLocationManager.delegate = self      
    }
 //: #3 - implement delegate functions
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        let myLocation = locations.first
        print ("my latitude = \(myLocation?.coordinate.latitude)")
        print ("my longitude = \(myLocation?.coordinate.longitude)")
    }
}

let whereAmI = WhereAmI()
*/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s