Tune your header file Part 1: Designated and Convenience initializers
Let’s talk about the header (.h) files. Probably you know that what is defined in the header file has a public visibility. Any method or property declared here can be accessible all of the other classes, which are importing this file. That is one of the reason why we need to plan carefully what is going to be in this file, which is sometimes referred as the public API of the class. There are 2 important things I wanted to discuss in this series, one is the designated initializer, the other is the nullable/notnull keywords. Usually you will use both of them in the Objective-C header files.
You probably have heard about them, but let me recap: Before we can use any object, first we need to allocate a memory address to it, and after the allocation we need to initialize it. Usually it is done by [[MyClass alloc] init] form. Obviously you are able to overwrite the default init method in the implementation file (MyClass.m), but there are other reasons not to do so.
One of the reasons is that your object probably need to have some other objects or variables handed over and set up when the object created, with its initial state. For example let say MyObject have two properties: (BOOL)isNightMode and (MyOtherObject *)theObjectDoingSomethingElse. If you want to instantiate an object from code and set up those properties, you probably will do in this way:
The problem with that solution is whether you, or any of your developer colleague can easily forget to set the isNightMode and theObjectDoingSomethingElse right after initializing the object. So the best practice is to pass those variables at the same time, when we instantiate the class, like this:
As you can see we are passing the values at the time of init, which means that we need to create a public method with the same signature (same count and type of parameters), and if we forget to do so we will have a build error when you compile your project. We will have a build error as well, if we want to use the defined method without all of the required parameters. As a best practice we also want this method to be the default initializer, which Apple calls Designated initializer. There is a macro defined for that purpose, which is the
NS_DESIGNATED_INITIALIZER. Don’t forget: You can have only one designated initializer, usually the one which calls
If we want to be fully covered we also need to take care of the default init method, since we don’t want anybody to use this anymore . There is a very nice trick with using the macro
NS_UNAVAILABLE. Just simply put the following code into your header file and the default init won’t be available anymore.
- (nullable instancetype)init NS_UNAVAILABLE;
Let’s put everything together in code then: My header file (MyObject.h) will look like this:
My implementation file will look like this:
//1 As you might noticed, I moved the properties from the header file to the implementation file (opening a new section, with @interface). In this way they won’t be part of the public API anymore, so they will be set at the init time, and can not be changed. In the other hand we lose the opportunity to change those properties from outside of the class. If you want to still change any of them, the easiest way is to move it back to the header file. Nevertheless, don’t forget the encapsulation principle: always try to challenge your decision when you want to expose a property to the publicity.
//2 Here is our new designated initializer. As you can see the parameters passed in the method parameter list have been assigned to the private variables (starting with underscore).
I think we did a good work here so far, but what if we need an initializer which won’t require the nightMode for our convenience? No worries, we can handle this, add the
initWithTheOtherObjectDoingSomethingElse:alreadyExisitingObject method to the MyObject.h:
We also need to add the implementation to the MyObject.m:
That’s it. The only drawback to this solution is that we made the isNightmode as a constant NO, but if we are doing it intentionally, then it’s OK. Usually this approach is used when we need a convenient way to initialize the object, keeping some of the parameters hard-coded. This is why it is called convenient initializer. I would suggest to have the designated initializer including all of the injected variables, and create convenience initializers which serve your needs.
The technique above is similar to dependency injection, but in order to have a proper dependency injection we need to involve protocols.
Let me know your thoughts!