iOS Communication Patterns Explained – Part 1: Preface
In this series of posts I would like to lead you through the most commonly used communication patterns in iOS development. I hope you have already heard about software design patterns. If your answer is no or never, the best advice I can give you to bookmark this blogpost and grab the basic knowledge about software design patterns. The first and authentic source is the Design Patterns book from the “Gang of Four”, but that book is a bit hard to digest. The easier option is to have the Head First Design Patterns book, which is based on Java, but I think it is easier to understand. If you have at least a basic understanding the patterns, then come back here and continue to read.
Our patterns
There are 4 main patterns, which are frequently used and can be implemented during your development. In the following list the first two are related the Observer design pattern, so the basic mechanism are similar as it is defined in the original Observer pattern. The Delegation pattern has been already defined as a design pattern, but the blocks are not real design patterns (maybe that’s why they are really straightforward and extremely efficient;)).
So, during designing and implementing your project in iOS development, you can use the following 4 communication methods:
– NSNotificationCenter
– Key-Value Observation (KVO)
– Delegates
– Blocks
So, why should we use communication patterns? There are several reasons to do it, but I think the most important is to decouple our code and apply the Single responsibility principle (SRP). If we apply the SRP we end up with classes which are carrying out one and only one, well defined function, but we still need to them to communicate with each other. Most probably those functions, which are implemented in those classes, need data as an input and probably they need to pass the output to other classes. In commonly used cases the output generated asynchronously, which means that using some background process or thread, and the thread needs to inform and communicate to the classes running on the main thread.
In order to show the key characteristics for each pattern I will create an example project and we can follow together the code change. My example project will be very simple, we want to store a picture with its URL. Obviously we need to download the picture, which needs to be done asynchronously, in order not to block the main thread. If we want to download the picture using the main thread, that will cause our application UI slow or “freeze”, since the main thread is used primarily the high priority tasks, such as keeping the UI responsive and receiving the input from the UI as well.
Basic project
So, let’s start with a basic project: File > New > Project. I will call the project CommunicationReFactory, and it will be in Objective-C.
When the project is ready, create our first Objective-C class, I will name it PMOPictureWithURL.
PMOPictureWithURL.h:
#import#import /** An example class for storing a downloaded image and it URL. */ @interface PMOPictureWithURL : NSObject //1 @property (strong, nonatomic, nullable) UIImage *image; @property (strong, nonatomic, nonnull) NSURL *imageURL; //2 - (nonnull instancetype)initWithPictureURL:(nonnull NSURL *)url NS_DESIGNATED_INITIALIZER; @end
As you can see I added a property to our class and a method.
1. The image property will hold our image, after it is downloaded. You might noticed the nullable keyword, which I personally like to use. Basically it helps you understand better what values can be passed or expected during the variable lifetime. As you ca see the other property is marked as nonnull, which means that the inmageURL must be provided, can not be null.
2. I replaced the default initializer, in order to have the image url from the very first moment when the class is instantiated. I also added the NS_DESIGNATED_INITIALIZER, which is a very handy macro to advertise the designated initializer of a class. Plus, Xcode will warn you to change your code accordingly, for example, you have to override the default init method.
Let’s take a look on the implementation, PMOPictureWithURL.m:
#import "PMOPictureWithURL.h" @implementation PMOPictureWithURL //1 - (instancetype)initWithPictureURL:(NSURL *)url { if (url) { self = [super init]; if (self) { _imageURL = url; _image = nil; } return self; } else { @throw [NSException exceptionWithName:@"Can't be initialised with null parameter" reason:@"Use [[PMOPictureWithURL alloc] initWithPictureURL:]" userInfo:nil]; return nil; } } //2 //Save the diagnostic state #pragma clang diagnostic push //Ignore -Wobjc-designated-initializers warnings #pragma clang diagnostic ignored "-Wobjc-designated-initializers" - (instancetype)init { @throw [NSException exceptionWithName:@"Not designated initializer" reason:@"Use [[PMOPictureWithURL alloc] initWithPictureURL:]" userInfo:nil]; return nil; } //Restore the disgnostic state #pragma clang diagnostic pop @end
As you can see I only implemented the initialisers.
1. This is our custom initializer. As you can see an exception will be thrown. if we are receiving nil as a nonnull parameter.
2. With the #pragma settings here we switch of the warnings from clang, saving its diagnostic state before. The init is also quite simple, it throws an exception if somebody wants to use it. The reason behind the approach that we want to have the URL of the image from the very first second, after the instantiation.
Finally, let’s implement the worst strategy to download the picture in our designated initializer:
- (instancetype)initWithPictureURL:(NSURL *)url { if (url) { self = [super init]; if (self) { _imageURL = url; NSData *data = [NSData dataWithContentsOfURL:url]; _image= [[UIImage alloc] initWithData:data]; } return self; } else { @throw [NSException exceptionWithName:@"Can't be initialised with null parameter" reason:@"Use [[PMOPictureWithURL alloc] initWithPictureURL:]" userInfo:nil]; return nil; } }
Why is it the worst strategy? There are (at least) two reasons: a) Because it blocks the main thread, and b) there is too much responsibility already in this class: holding the data, and gathering it through communication via the network.
Stay tuned, in the next post I will split and refactor the code, and we will see, how to use and implement the NSNotificationCenter solution for this particular problem.
The next post available here.