I have general question about designated initializer. I have a some class and from there i want to call a initializer, but before i started to fill my #properties with passing data i want to make data default. For example:
-(id)initWithDefault:(NSDictionary*)defaultTemplate
{
self = [super init];
_fontColor = [defaultTemplate objectForKey:#"color"];
_fontSize = [[defaultTemplate objectForKey:#"size"] intValue];
return self;
}
-(id)initWithTemplate:(NSDictionary*)template
{
self = [self initWithDefault:myDefaultTemplate];
//now i doing something with my template
return self;
}
Is this is a way to prevent null #properties? It this a correct use of designated initializer? Of course you can assume that myDefaultTemplates is not null, and has not null object in keys.
This seems fine with me. I would use the safe way (presented below), but otherwise your code is fine.
-(id)initWithTemplate:(NSDictionary*)template
{
if(self = [self initWithDefault:myDefaultTemplate]) {
//now i doing something with my template
}
return self;
}
Your implementation is perfectly fine, given the fact that _fontColor and _fontSize variables are your local variables of properties.
adig's suggestion is just an enhancement on what you already have implemented. This check takes care of the situation, when your object does not get allocated due to any reason.
Related
Here is initialization as a class method:
+ (instancetype)imageEditorForDocument:(ImageDocument*)imageDocument
{
ImageEditorViewController* result = nil;
result = [[ImageEditorViewController alloc] initWithNibName:#"ImageEditorViewController"
bundle:[NSBundle mainBundle]];
if ( result )
{
result.imageDocument = imageDocument;
}
return result;
}
And here is an initialization as an instance method:
- (instancetype)initWithDocument:(ImageDocument *)imageDocument
{
self = [[ImageEditorViewController alloc] initWithNibName:#"ImageEditorViewController"
bundle:[NSBundle mainBundle]];
if( self )
{
self.imageDocument = imageDocument;
}
return self;
}
As far as I can tell, the only difference is that the sender doesn't need to call alloc when using a class initializer
But is there any other reason?
Also, a side question is there a name for a initializer that is a class method?
Like in NSColor
+ (NSColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha NS_AVAILABLE_MAC(10_9);
With manual reference counting (MRC), the difference was the factory method typically returned an autoreleased instance while the instance method returned an instance that the caller now owned.
With automatic reference counting (ARC), that difference is now hidden from the calling code.
So the only difference is convenience for the caller as well as slightly more work for the author of the class to write the optional factory method.
BTW - your instance method initializer is incorrect. It should be more like this (not the proper way to set self):
- (instancetype)initWithDocument:(ImageDocument *)imageDocument
{
self = [super initWithNibName:#"ImageEditorViewController"
bundle:[NSBundle mainBundle]];
if( self )
{
self.imageDocument = imageDocument;
}
return self;
}
And given this, your factory method should really be:
+ (instancetype)imageEditorForDocument:(ImageDocument*)imageDocument
{
ImageEditorViewController* result = [[[self class] alloc] initWithDocument:imageDocument];
return result;
}
Yes there is a major difference. If you implement a factory method (class method) you could for example choose to return an already existing instance of your object (e.g. from some sort of cache) instead of creating a new instance. Imagine you have a class Country which is expensive to initialize. Hence, write the following factory method that looks up the cache first and only if it does not find the country it creates a new object:
+(instancetype) countryForCountryCode: (NSString *)countryCode
{
Country *country = ... // ask our "CountryCache" if we already have a cached instance of the country
if (!country) {
country = [[Country alloc] init];
// Here you would also set up the new Country object, or even write a "private" initializer
// You would also add the new instance to the cache here
}
return country;
}
On the other hand, when you choose a "conventional" initializer the caller will always have created a fresh instance via alloc before your initializer gets called, and you'll have no way of returning a cached object instead.
Lastly I just remembered that I personally use factory methods whenever I deal with persistent objects (if not using CoreData). So, if I want to get a new object from the database I usually implement a factory method called "load". And to actually create a new record in the database I would implement another factory method named "create". If you would use initializers here it would become very confusing.
I was reading through the source of core-plot and ran across this code in CPTColor:
-(id)initWithCGColor:(CGColorRef)newCGColor
{
if ( (self = [super init]) ) {
CGColorRetain(newCGColor);
cgColor = newCGColor;
}
return self;
}
-(id)initWithComponentRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha
{
CGFloat colorComponents[4];
colorComponents[0] = red;
colorComponents[1] = green;
colorComponents[2] = blue;
colorComponents[3] = alpha;
CGColorRef color = CGColorCreate([CPTColorSpace genericRGBSpace].cgColorSpace, colorComponents);
[self initWithCGColor:color];
CGColorRelease(color);
return self;
}
If [self initWithCGColor:color]; results in initWithCGColor's superclass assigning a different value to self, will that new value be returned from initWithComponentRed:green:blue:alpha:?
In other words, is self in the two init methods the same variable two distinct variables?
This setup is potentially fragile, and Joe's suggestion, assigning self in initWithComponentRed:... before returning that value will fix it.
Every method gets an argument, self, that points to the instance it was called on. The argument acts just like any other variable; there's nothing special about it. That's why it's possible to assign self, and why it's necessary to have an explicit return in initializers.
When you send [super init], you're using the superclass's version of the init method, but the current instance will still be passed as self. Since initializers in Objective-C are allowed to destroy the object they receive and create and return a new one,* there is a potential for self = [super init] in initWithColor: to not be the same object as that pointed to by self back in initWithComponentRed:....
If that happens, since you don't reassign self to point to that new, correctly-initialized object, you'll be returning an uninitialized object from initWithComponentRed:....
This is only a practical concern if your superclass could possibly do the substitution I described, but there's also no reason, AFAIK, to not do that reassignment.
*Collections do this, for example, because they don't know how big they need to be at alloc time; I believe NSNumber also does this because it has cached values for instances representing small integers.
No if self were to change (ex. if you were to implementing a color cache) initWithComponentRed:green:blue:alpha: would return the wrong object unless the code was changed to:
...
self = [self initWithCGColor:color];
CGColorRelease(color);
return self;
}
self always refers to the same object. It is not a "variable", but a language key word. If one initializer is called after the other, the object is simply altered.
NB: Otherwise, your pour class would have a "split personality" ;-).
This is currently what I have for my init,
- (id)init
{
self = [super init];
if (self) {
self.url = [[NSURL alloc] init];
self.blurb = [[NSString alloc] init];
self.author = [[NSString alloc] init];
}
return self;
}
It does nothing, but I have another method called initWithObject: that will use its argument to fill up the instance variables url, blurb, and author. I don't know what I should be doing with this init. Should I throw an exception? What other options do I have?
If you want to override your standard -init method you could either return nil (if you do not want -init to be used) or do:
- (instancetype)init
{
return [self initWithObject:nil];
}
If you want to stop the use of -init completely you can tag it as an unavailable attribute or use NSAssert:
// .h
// ...
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
// ...
// .m
- (instancetype)init
{
NSAssert(NO, #"This method should not be used");
return nil;
}
You can use either UNAVAILABLE_ATTRIBUTE or NSAssert(), but if you use UNAVAILABLE_ATTRIBUTE you need some kind of implementation of -init, even if it just returns nil.
You don't have to have a plain init—you can simply have your initWithObject: method. If you're doing any basic setup that will remain the same 80% of the time, or if you have some common code in all your initializers, you can override init, but you are not required to.
Also consider changing your initWithObject: name to be more specific, to something like initWithPost: (I'm assuming this is some kind of blog-entry fetcher based on your ivars) so it's more apparent what object is desired.
I think you misinterpreted what you read. I don't think you would throw an exception. You could; however, leak memory. If your initWithObject: method looks like this:
- (id)initWithObject:(id)obj {
if ((self = [self init])) {
self.url=[obj url];
self.blurb=[obj blurb];
self.author=[obj author];
}
return self;
}
And you would be perfectly fine. You could get an exception if your object was instantiated with -init and you used a variable which was assigned, assuming it was real. So in your subsequent methods be sure to check that the objects exist before using them.
If you made your object with -init rather than -initWithObject this could throw an exception:
- (void)dealloc {
[url release];
[blurb release];
[author release];
[super dealloc];
}
The rule that Apple has established for Cocoa programming is that every class must have one initializer method which is the "Designated Initializer". Every other initializer for the class must call that D.I.* The D.I. itself must call the superclass's D.I. Generally, the initializer with the greatest number of arguments (the one that most completely specifies the state of the new object) is the D.I.
In your case, with the bare init, and initWithObject:, the second would likely be the D.I. You would therefore override init to call initWithObject: with some default argument:
- (id) init {
return [self initWithObject:[Object objectWithURL:[NSURL URLWithString:#"http://www.apple.com"]
blurb:#""
author:#""]];
}
This will result in a sort of dummy object, which is correctly initialized with useless data. (Outside of ARC, be sure to watch the memory management of the default argument(s) -- you want to use an autoreleased/unowned object(s).)
*Sometimes an exception is made for initWithCoder:.
If you have any method that you don't want called and that you don't want your subclass to support, throwing an exception in a Debug build is perfectly reasonable.
What is the proper way of overloading init methods with variable param number? I'm doing it:
- (id)initWithInt:(int)a
{
return [self initWithInt:a andString:nil];
}
-(id)initWithInt:(int)a andString:(NSString*)str
{
self = [super init];
if (self) {
NSLog(#"%# %i",str,a);
}
return self;
}
It works, but the return [self initWithInt:a andString:nil]; does not seem right to me(there is no self at the moment, right?)
No, this is perfectly valid. Self is already setup, the idiom of reassigning self is for a couple of different edge cases (initialization fails, the initializer wants to return a different instance then the one you allocated, etc).
I've read in many places that you should always initialize Objective-C objects like so:
- (id) init {
if (self = [super init]) {
....
}
return self;
}
Because the super's init method may return a separate object from the current self.
Now I'm trying to do something like this, and I'm not sure if I have it right, vis-a-vis how retaining and releasing should work:
- (id) init:(int)idx {
id obj = [Cache findSelf:idx];
if (obj) {
[self release];
self = [obj retain];
} else {
self = [self doLoad];
}
return self;
}
I'm mostly curious if this is the correct way to do the retaining and releasing of self and obj. Is there a better way?
You're correct about the self = [super init] part, since some Cocoa classes actually do return a different object than the one that was allocated. However, this is the exception rather than the rule, and doing so in your own code should be exceedingly rare or not done at all. Although it may be tempting to intercept -init calls, you'd be going against the grain of established convention and what Objective-C programmers expect the code to do.
This type of -init method is generally a bad approach, since -init methods should be as straightforward as possible, and should really be concerned with initializing the object. I'd probably write a convenience method like this:
+ (id) instanceForIndex:(NSUInteger)index {
id obj = [Cache findSelf:index];
if (obj == nil) {
obj = [[self alloc] init];
// Add to cache
}
return [[object retain] autorelease];
}
Then call this method instead of -init. This will make the -init logic much cleaner.
Also, I'm not sure what your Cache class does, but it could be worth rethinking that implementation, and using a hidden static variable to store instances (for example, an NSMutableDictionary, where the key is an NSNumber created from the index). This SO question may be of use.
I agree with Quinn that you should use a convenience class method. Still, I think that your init method is mostly correct, except in your else clause you need to call the parent initializer, i.e. self = [super init].