Let’s say you are programming a flashcard app. In the model for these flashcards, you will have a flashcard object, and each flashcard has 2 strings as properties (one for the front of the card, and one for the back). You could just alloc-init the card, and then set them later… but what use is a blank flashcard? Why not only initialize a flashcard when those piece of data are provided? That is what custom initializers are for.
Here is an example of making this flashcard class with its own custom initializer.
In FlashCard.h:
@interface FlashCard : NSObject
@property (strong, nonatomic) NSString *frontText;
@property (strong, nonatomic) NSString *backText;
- (instancetype)initWithFront:(NSString *)onFront back:(NSString *)onBack;
@end
In FlashCard.m:
#import "FlashCard.h"
@implementation FlashCard
- (instancetype)initWithFront:(NSString *)onFront back:(NSString *)onBack
{
if ((self = [super init])) {
_frontText = onFront;
_backText = onBack;
}
return self;
}
@end
And that is pretty much all there is to it. While it is not a lot of text, there are a few things that I had to look up a bit to understand what was going on in these statements, so I will go into more depth to explain some of the new things shown here.
Super init and instancetype
What is instancetype? We’ll get there, but to make it make the most sense, I need to skip ahead a moment.
On that if-statement line, you see how we assign the result of [super init] to self? For that part, we are calling the initializer of the superclass that this class is inheriting from. In the header file, you can see that the class inherits from NSObject (as most classes we make do, at least after several levels of inheritance). That means you just assigned an NSObject* to the self variable. Well, technically, you assigned an id to it, but it is an id that stands for an NSObject*.
I am still learning this, but from what I’ve found out so far, that is part of the reason for that wacky instancetype return type. Previously in Objective-C, you would just set the return type to “id”, but now you can use instancetype instead. It basically tells the compiler that it will return an object that is the same type this message (init) was sent to, in this case being the “Flashcard”. I am still not entirely sure why you cannot tell it that it will return a (Flashcard *), but I think it is related to the fact that we just instantiated it as an NSObject*. When I learn more to better explain it, I will update this post, but for now, here is a post from NSHipster about instancetype.
So anyway, back to that if statement. That kind of crazy “conditional” there as I said before, is setting the result of [super init] to the self variable. If the initialization fails for some reason, that message will return nil. If it returns nil, then you will exit that if statement, and just return what is in self, which is nil (to say that this initialization also failed). If it does successfully initialize, it will put that NSObject* (or id of one) into self, and continue in to the if statement.
Writing to instance variables directly?!
Now, inside there I am doing something that you in general should not do, but from what I’ve found, is recommended in the initializer. As we saw in the .h file, I have made 2 public properties for frontText and backText. When I make a property, as I’ve mentioned before in my post Objective-C Classes and Usage of Variables, Objective-C automatically makes setters, getters, and an instance variable (aka “field”) to store them. The default instance variable it makes is the name of the property preceded by an underscore (so _frontText and _backText).
In most other places in code, you should only use the properties, such as self.frontText and self.backText, and you technically can here, but you may not want to. The main reason I have found is that in more advanced programs, the setter may depend on a fully formed object to fulfill its duty. In this program, it probably wouldn’t matter, but if I had another string, that would store both the front and back in the same string for some reason (like to list off the total contents of the flashcard), and that string was updated when you used the setter. It would read the string you just set, and it would try to read the string that you haven’t written yet. You would be better off just writing to the instance variables directly, and then calling that update method. It is a contrived example, but that would be one reason you might not want to.
Anyway, after you set initialize self itself, and then fill in those instance variables, all you have to do now is return self to the class that called this, and you have yourself a fully formed Flash Card. If there are default values, you could override init, and fill them in. If there aren’t default values, you could just return nil from that overridden init.
I hope you found this article helpful. If you did, don’t hesitate to share this post on twitter or your social media of choice. The blog is still pretty new, and every share helps.
Thanks!