iOS Communication Patterns Explained: Part II - NSNotificationCenter
In the second post of the iOS communication patterns, we are going through an implementation of an NSNotificationCenter-based solution.
Join the DZone community and get the full member experience.Join For Free
In the second post of the iOS communication patterns, we are going through an implementation of an NSNotificationCenter based solution. This implementation won’t follow the best practices (more about it in the conclusion), I wanted to give you a good grasp of it.
First of all, let’s talk about the Notification Center. If I want to make an analogy it is something like the Twitter for the Objective-C or swift objects on your device. Your objects can follow other objects and when the other object sends out an information the follower object will be notified. Actually, the analogy could be much better if we assume that you can have a notification from twitter for a given hashtag mention… And also don’t forget, that the whole world will see your hashtag.
If you want to drop the idea to compare Notification Center to something, then we are ending up that the Notification Center is a custom Observer software design pattern implementation: the observed entity needs to send a labeled message to the Center, and the Center will notify everyone, who is subscribed to the label. In order to avoid confusion, the label should be unique.
How to Implement
We need the following things to use Notification Center:
– One (or more) labels (usually an NString) which uniquely identifying the purpose of the message:
– An entity (class) which sends a labeled message to the Notification Center:
[[NSNotificationCenterdefaultCenter] postNotificationName:PMODownloadWasSuccessful object:self userInfo:nil];
– One or more entities subscribing to the labelled notification, and preferably do an action (calling didPictureDownload in this case):
[[NSNotificationCenterdefaultCenter] addObserver:self selector:@selector(didPictureDownload) name:PMODownloadWasSuccessful object:nil];
– And the most important thing: unsubscribe from the notifications in case of our class don’t need to listen to the notification anymore.
A Little Refactoring and Design
First of all, I would like to refactor our previous solution and trying to come up with a reasonably modular design. The first step is split our class. At the moment we have this class:
I would like to separate the pure data from the logic. You probably ask, why? My answer is, because it is a good practice to separate the dataset type objects from the objects that are doing some function on the datasets. It is also a kind of separation of concern. On the top of that the dataset object should be immutable, which means when its data already set, then we can not modify it. That will help us to prevent accidental modifications. If we need to change a dataset, we need to replace it with a newly created instance.
We can also hide the used storage implementation, by creating a new class, which will serve as a public interface for the other part of our application.
So we are going to create this structure:
There is also a problem with the code, which downloads the actual image. The problem is still the separation of concerns: our model controller doesn’t need this responsibility. Let’s go and create a class, which helps us with downloading something:
Finally, we separated the concerns and the areas of the responsibility: We have a class responsible for storing the data (PMOPictureWithURL), a class that managing this storage (PMOPictureController), and a class which is responsible for the network operations (PMODownloader).
Let’s do the necessary changes in our classes, first, create the PMODownloader:
As you can see, we just moved out the network communication code from the PMOPictureWithURL.
Let’s change PMOPictureWithURL, which will be our storage class:
Finally create the PMOPictureController:
I think it is quite straightforward. There is a designated initializer with only one mandatory parameter, the image url. I put the downloading event trigger into a different method, os the class itself won’t start immediately the download of the image. Finally we have a method, where we can retrieve the downloaded image. The implementation file will be the following:
There are small tricks at those classes. At //1 I created some macros. Macros are particularly helpful, when you want to shorten an expression, especially when you need ti use the expression more than ones in your class. If you carefully select your macro name, it tells you more about the function than the whole line of code.
At //2 there is a @interface section in the implementation file. If you are already familiar with it, just skip this part, but if it is new for you, I suggest to study the syntax again. Adding an interface section to the implementation file gives you the ability to create private methods and properties. As you can see I didn’t want to expose the actual data storage to the public, that’s why it is declared here.
Getting Our Hands Dirty
Phew. We have done so much so far, and we have just arrived to the point when we can actually talk about the Notification Center. As I mentioned our communication needs a “hashtag”, a unique identifier to broadcast the message to the world. The best way is to out this identifier in a separate header (.h) file. Why is it a good practice? Because we don’t need to include the sender class in every other class, which wants to listen to our hashtag message, it is enough to include the header file. Let’s create a header file (in Xcode File->New->File…-> Header File), and call it PMODownloadNotifications.h.
As you can see, I defined 2 message identifier. One for the case of success, and the other one for the failure.
Our next step is to implement the code, which sends out the respective message notification to the Notification Center. That will be implemented in PMODownloader, and I also change the networking code, to be asynchronous by using NSURLSession API. Since we are not going to return with the NSData from the
downloadDataFromURL method, we need to modify the header file accordingly.
At //1 I just implemented a classic NSURLSession scenario, by creating an NSURLSessionDataTask to download the image. This task will run on a background thread, so we have just fixed the error of blocking the min thread. When the task is finished, it will call the block defined in the
completionHandler, and it will invoke one of the notification methods defined at //2. As you can see if there is an error the code calls the error notification method, if everything was OK, then we are passing the downloaded data to the method, which is going to notify everybody, who is subscribed via the Notification Center.
Let’s start to listen to those messages in PMOPictureController. Change the
downloadImage method to listen to the message, and call an operation:
//1 In order to set up the listening part, in PMOPictureController.m, add the import for the notification strings.
//2 We slightly changed the public download script. The setup for listening to the notification will be in a separate method, you can find it under //4. We kept this method simple, by creating an instance of the downloader, and passing the url for further processing. The key point is here, that the downloader will start the download, but our current class has nothing to do with it, until we are not receiving a notification about the task ended in the background.
//3 I created the helper methods for each notification, what we predefined. If the result is success, we are updating our data model image, if the result is failure we just making a log entry. Please note, irrespective of the result, the class is going to unsubscribe from the notifications, calling
//4 Here are the helper methods for subscribing and unsubscribing for the notifications. Note, that the selector
didImageDownloaded: has a colon at the end. This is required to pass parameters to this particular method. We are going to pass an
NSNotification object to this method, which will contains a dictionary, called
userInfo. We have already ejected our image raw data into this dictionary in the PMODownloader.
//5 As a good citizen, at the time of the object deallocation we are going to unsubscribe from the notifications as well. Don’t forget, it could easily happens that your object has been already destroyed, when the asynchronous download thread finished.
So, we have everything let’s create a Unit test. Actually, we should start with a unit test, so next time don’t forget it;).
You have probably already written some unit tests. If not, then there is a really good article by Hoang Tran.
The only problem is that we have an asynchronous process to test, so we need to use a bit different XCTestCase, as it is used with synchronous, or linear executed processes. The trick is the XCTestExpectation object, which is designed especially for those cases.
This is my Unit test for the download, I think it is straightforward, and easy to understand.
There is a small issue while you are running the tests. During the test your notification sent to and received by your test classes will be also spread out into your running application! Which means if your Storyboard Entry point ViewController is listening to the same notification, then it will be notified as well!
Conclusion and Key Takeaways
Use this pattern for 1 to N communication inside the Model, when more than one object needs to be informed, or the communication from the Model to the (View)Controller . Actually for the async downloading, and passing the downloaded data as a payload in the notification the is not the best solution. We will see better approaches in the upcoming blog posts.
The key points about the implementation, again
- Create a unique notification identifier
- An object which sends notification to the Notification Center
- Another object(s) which needs to be subscribed for the notification in the Notification Center before the sender object starts to send the notification.
- Proper unsubscribe mechanism in the listener classes.
In the next blog post, we are going to change the approach to KVO (Key-Value Observing) based solution.
Published at DZone with permission of Peter Molnar, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.