Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

iOS Communication Patterns Explained, Part IV: Delegation

DZone's Guide to

iOS Communication Patterns Explained, Part IV: Delegation

Delegation is powerful in terms of segregation of concerns, which leads us to cleaner code. For asynchronously downloading an image, delegation is not the best solution.

· Mobile Zone
Free Resource

Launching an app doesn’t need to be daunting. Whether you’re just getting started or need a refresher on mobile app testing best practices, this guide is your resource! Brought to you in partnership with Perfecto

In the last post, we changed the communication between our classes to be based on the Key-Value Observation. As we can see, KVO is not the best solution for our problem, but sometimes can be helpful if we need to observe one particular object property changes.

In this post, I will modify the communication to have the same characteristic as the delegation software design pattern. Let's see what changes are required in the class diagram in order to achieve our target:

In the UML diagram above, you see two «protocol», which is pretty much same as the «interface» in other Object-Oriented Programming languages (if they support this particular principle). The protocol or the interface sometimes called as a contract, and all of the classes that implement the protocol or interface will have a “contractual obligation” to implement all of the required properties and methods. I would more think about the protocols as a template, where all of the required parts needs to be filled out. If we are referencing to a protocol in Objective-C, the form is used, where ProtocolWantToConform is the name of the protocol.

About the Delegation Pattern

The delegation pattern originally was created for allowing something similar to multiple inheritances, which is not available in most of the programming languages. In our Apple software ecosystem, the pattern is more about acting on behalf of another object. Usually, delegates are for accomplishing well-designed (and limited) functionality, helping the developers have a nicely decoupled software. The best examples of delegation patterns are the UITableViewDelegate and UITableViewDataSource protocols. With a careful software design, you can use separate classes and code to provide the behavioral part (from the delegate) and the data source for your table view. The benefit to doing so is that you can later easily change any of them without making significant changes in the table view controller itself.

About the Protocol Files

I have already tried to explain what a protocol is. Let's see how can you create a protocol file. Don’t forget the protocol should stand only as a blueprint for the classes want to conform to or implement the protocol. Having said that, we don’t need to implement any details in the methods; only the method signatures. Optionally, we can also specify if the method is optional.

Creating a protocol file is a bit tricky in Xcode, but you can get used to it quite easily. Go to File > New > File, and on the screen, select the Objective-C file (marked with a small m), as you can see in the screenshot below:

Click on Next and select Protocol as File Type. Give it a reasonable and meaningful name.

Eventually, you will end up only one file with the .h extension and a quite simple @protocol NameOfProtocol @end structure in the file. That is the parent or super-protocol!

Protocol Files for Our Solution

As we can see in the UML diagram, there are two protocol files used: PMODataHolder and PMODownloaderFromURL. We need to establish a two-way communication, or at least cross-reference between those entities via the delegate and the receiver properties. Keep in mind that we need the receiver property to hold the reference of the image holder object in the delegate protocol in order to make a callback on it with the downloaded data.

PMODataHolder.h:

#import <Foundation/Foundation.h>
#import "PMODownloaderFromURL.h"

/**
Forward declaration of the used and referenced protocol
*/
@protocol PMODownloaderFromURL;


@protocol PMODataHolder <NSObject>

/**
Our delegate, which will do the actual download for us.
*/
@property (strong, nonatomic, nullable) id <PMODownloaderFromURL> delegate;

/**
Completion handler. Call with the downloada data as NSData when the download of the data completed.
@param data The downloaded data is raw NSData format.
*/
- (void)didDownloadedData:(nullable NSData *) data;

@end

I think it is a well-commented file. There are a few things I would like to emphasize.

Please note the forward declaration of the cross-referenced protocol for the delegate class, which was required by Xcode, to build the solution. Without this, I had a nasty “no type or protocol named” error.

The second thing I want to emphasize regards the delegate property declaration. As you can see, the delegate will be a strong attribute. The other thing you should note is that the delegate type is ID. This kind of declaration can make your code loosely coupled since it won’t tell you what kind of object we want to reference here, only that we require that this class need to implement a certain type of protocol. I think this is one of the important techniques which helps you to apply the principle from the GoF design patterns book Program to Interfaces, Not Implementation.

PMODownloaderFromURL.h 

#import <Foundation/Foundation.h>
#import "PMODataHolder.h"


/**
Forward declaration of the used and referenced protocol
*/
@protocol PMODataHolder;

@protocol PMODownloaderFromURL <NSObject>

/**
A weak pointer, pointing back to the original caller. We can do a callback message sending via this, when the async download finished.
*/
@property (weak, nonatomic, nullable) id <PMODataHolder> receiver;


/**
A general downloader method, which is required to implement.
@param url The url of the source file.
*/
- (void)downloadDataFromURL:(nonnull NSURL *)url;

@end

The only change here about the cross-declaration is the weak attribute of the receiver property. This is because I would like to avoid retaining circles. Using the weak pointer means that if the receiver object for some reason has been already deallocated (for example, the download process takes a long time and the user has been already moved to another screen), the receiver object will be successfully freed from the memory by the ARC.

Changes in Our Files

PMOPictureController.h 

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
//1
#import "PMODataHolder.h"

//2
@interface PMOPictureController : NSObject <PMODataHolder>

/**
The designated initializer, the picture's url is mandatory to pass.
@param url The picture full url, as NSURL
@return An instance PMOPictureController.
*/
- (nonnull instancetype)initWithPictureURL:(nonnull NSURL *)url NS_DESIGNATED_INITIALIZER;


/**
Start the asynchron download of the image.
*/
- (void)downloadImage;

/**
A high level, public API function to retrieve the image as UIImage
@return an instance of UIImage.
*/
- (nullable UIImage *)image;


/**
Removinf the default initalizer
@return nil
*/
- (nullable instancetype)init NS_UNAVAILABLE;

@end

As it was described in our new UML diagram, the PMOPictureController needs to implement the PMODataHolder protocol. At //1 ,  the protocol header file imported, and at //2 , it is declared that this class needs to conform to the said protocol.

PMOPictureController.m

#import "PMOPictureController.h"
#import "PMODownloader.h"
#import "PMOPictureWithURL.h"
#import "PMODownloadNotifications.h"


@interface PMOPictureController()

/**
Our private data class, storing and hiding the information.
*/
@property (strong, nonatomic, nullable) PMOPictureWithURL *pictureWithUrl;


@end

@implementation PMOPictureController

// 1
@synthesize delegate = _delegate;

#pragma mark - Initializers
- (instancetype)initWithPictureURL:(NSURL *)url {

self = [super init];
if (self) {
_pictureWithUrl = [[PMOPictureWithURL alloc] initWithPictureURL:url];
PMODownloader *delegate = [[PMODownloader alloc] init];
delegate.receiver = self;
_delegate = delegate;
[self addObserverForDownloadTaskWithDownloader];
}
return self;
}


// 2
#pragma mark - Public API
- (void)downloadImage {
[self.delegate downloadDataFromURL:self.pictureWithUrl.imageURL];
}

//3
#pragma mark - Implementation of the PMODataHolder protocol
- (void)didDownloadedData:(NSData *)data {
if (data) {
[self willChangeValueForKey:@"image"];
self.pictureWithUrl.image = [UIImage imageWithData:data];
[self removeObserverForDownloadTask];
[self didChangeValueForKey:@"image"];
}
}

#pragma mark - Accessors
- (UIImage *)image {
return self.pictureWithUrl.image;
}


#pragma mark - Notification Events
- (void)didImageDownloadFailed {
NSLog(@"Image download failed");
}


#pragma mark - Notification helpers
- (void)addObserverForDownloadTaskWithDownloader {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didImageDownloadFailed)
name:PMODownloadFailed
object:nil];
}


- (void)removeObserverForDownloadTask {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}


#pragma mark - Dealloc
- (void)dealloc {
[self removeObserverForDownloadTask];
}

@end

Take a look at the @synthesize at //1 . If you remember, I created a property in the protocol, but I also said that the protocol file is just a blueprint; the developer’s work is to build a class from this blueprint. Unfortunately, the properties declared in the protocol won’t be automatically synthesized for us. That’s why we need to implement them by synthesizing them. In very simple terms, this line creates a private variable in the class called _delegate, which will be bound to the self.delegate property. It also creates the default accessors for the self.delegate. Make a note about the initializer changes: It will set up the PMODownloader as a delegate immediately after the default initialization.

At //2, we simply pass the task to our delegate. This is the part where the pattern is actually implemented, i.e., it asks another class to do something on behalf our controller.

At //3, we have the PMODataHolder protocol, including this method. I kept the other part of the class unchanged, so we still notified in any download problems, and the controller also notifies the objects, which are observing the image Key-Value change.

So, let’s take a look at our downloader class, how the protocol will be implemented, and what has been changed:

 PMODownloader.h 

#import <Foundation/Foundation.h>
//1
#import "PMODownloaderFromURL.h"

//2
@interface PMODownloader : NSObject <PMODownloaderFromURL>

/**
Property to store the downloaded data in NSData format
*/
@property (strong, nonatomic, nullable) NSData *downloadedData;


@end

At //1 , I import the protocol file, so in the line at //2, I can use it with the class declaration that this particular class will implement the protocol. As you can see the forward declaration for  - (void)downloadDataFromURL:(nonnull NSURL *)url; has been removed since now it is declared in the protocol.

PMODownloader.m 

#import "PMODownloader.h"
#import "PMODownloadNotifications.h"

@implementation PMODownloader

//1
@synthesize receiver = _receiver;

//2
#pragma mark - Public API / Protocol implementation
- (void)downloadDataFromURL:(NSURL *)url {

NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];

NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
[self notifyObserverDownloadFailure];
} else {

[self.receiver didDownloadedData:data];
}
}];
[task resume];

}


#pragma mark - Notifications
- (void)notifyObserverDownloadFailure {

[[NSNotificationCenter defaultCenter] postNotificationName:PMODownloadFailed
object:self
userInfo:nil];
}


@end

At //1, I use the same technique with the _receiver property that was used PMOPictureController.m.

Maybe you have already noticed that the code is getting more clear, and it is definitely seen in the method at  //2, where all of the other calls just simply replaced by a callback to the receiver and pass the downloaded raw data.

I also removed the other, now useless code, but I kept the one for notifying about download failure.

Updated Test Cases

As I changed the strategy, how to carry out the download, actually hiding the downloader class behind the delegation pattern, our tests need to be updated as well.

PMOPictureControllerTests.m 

#import <XCTest/XCTest.h>
#import "PMOPictureController.h"
#import "PMODownloadNotifications.h"

@interface PMOPictureControllerTests : XCTestCase
@property (strong, nonatomic) PMOPictureController *pictureController;
@end

@implementation PMOPictureControllerTests

- (void)setUp {
[super setUp];
// Set up the controller with a valid image URL.
self.pictureController = [[PMOPictureController alloc] initWithPictureURL:[NSURL URLWithString:@"https://upload.wikimedia.org/wikipedia/en/4/45/One_black_Pixel.png"]];
}

- (void)tearDown {
// Being a good citizen, and nil out the strong reference, so we can safely initialize again a new instance
// for the next test
self.pictureController = nil;
[super tearDown];
}


/**
Test the asynchron download
Internet connection required to pass this test!
*/
- (void)testPictureAsyncDownload {
XCTestExpectation *expectation = [self keyValueObservingExpectationForObject:self.pictureController keyPath:@"image" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) {
[expectation fulfill];
return true;
}];

[self.pictureController downloadImage];
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
if (error) {
NSLog(@"Timeout Error: %@", error);
}
}];
}

/**
Testing the download failure wiht a nonexisting domain
*/
- (void)testPictureAsyncDownloadFailed {


self.pictureController = [[PMOPictureController alloc] initWithPictureURL:[NSURL URLWithString:@"https://ThereIsNoSuCHDomainName/MssedUPName.png"]];

XCTestExpectation *expectation = [self expectationForNotification:PMODownloadFailed
object: nil
handler:^BOOL(NSNotification * _Nonnull notification) {
[expectation fulfill];
return true;
}];

[self.pictureController downloadImage];
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
if (error) {
NSLog(@"Timeout Error: %@", error);
}
}];
}



@end

As you can see, our download test is now based on a KVO on the PMOPictureController image property.

PMODownloaderTests.m 

#import <XCTest/XCTest.h>
#import "PMODownloader.h"
#import "PMODownloadNotifications.h"

@interface PMODownloaderTests : XCTestCase
@property (strong, nonatomic) PMODownloader *downloader;
@end

@implementation PMODownloaderTests

- (void)setUp {
[super setUp];
self.downloader = [[PMODownloader alloc] init];
}

- (void)tearDown {
self.downloader = nil;
[super tearDown];
}


/**
Testing the download failure wiht a nonexisting domain
*/
- (void)testPictureAsyncDownloadFailed {



XCTestExpectation *expectation = [self expectationForNotification:PMODownloadFailed
object: nil
handler:^BOOL(NSNotification * _Nonnull notification) {
[expectation fulfill];
return true;
}];

[self.downloader downloadDataFromURL:[NSURL URLWithString:@"https://ThereIsNoSuCHDomainName/MssedUPName.png"]];
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
if (error) {
NSLog(@"Timeout Error: %@", error);
}
}];
}


@end

I removed the successful download test since this part of the code should work via the delegation pattern.

Wrapping Up

I hope you've gained some knowledge from this post. Delegation is quite powerful in terms of segregation of concerns, which leads us to clean(er) code. It needs some preliminary planning and design by using the “program for interfaces, not implementation” principle.

For that particular case, i.e., asynchronously downloading an image, delegation is not the best solution still. In the last post of this series, I will introduce the block-based solution.

Keep up with the latest DevTest Jargon with the latest Mobile DevTest Dictionary. Brought to you in partnership with Perfecto.

Topics:
mobile ,ios ,communication patterns ,delegation

Published at DZone with permission of Peter Molnar, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}