Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is "self = [[Rectangle alloc] init]" in a class method BAD?

In the document "Objective-C Programming Language" by Apple, page 48 says:

+ (Rectangle *)rectangleOfColor:(NSColor *) color
{
    self = [[Rectangle alloc] init]; // BAD
    [self setColor:color];
    return self;
}

+ (id)rectangleOfColor:(NSColor *)color
{
     id newInstance = [[Rectangle alloc] init]; // GOOD
     [newInstance setColor:color];
     return newInstance;
}


+ (id)rectangleOfColor:(NSColor *)color
{
     id newInstance = [[self alloc] init]; // EXCELLENT
     [newInstance setColor:color];
     return newInstance;
}

One is bad, one is good, and the other is excellent. Why is that?

like image 906
Proton Avatar asked Mar 15 '13 15:03

Proton


3 Answers

There is a fourth pattern....

(1) type mismatch is BAD.

(2) static reference to class yields method that won't behave correctly in subclasses

(3) dynamic reference to class means subclasses will be instantiated as subclass instances


(4)

+ (instancetype)rectangleOfColor:(NSColor *)color // Über-bestest evar!
{
     Rectangle *newInstance = [[self alloc] init];
     [newInstance setColor:color];
     return newInstance;
}

llvm added the instancetype keyword that says "yo! this method returns an instance of whatever class it was called on". Thus, if you were to subclass the above, you could:

RectangleSub *rs = [RectangleSub rectangleOfColor:[NSColor paisleyColor]];

But this would warn (beyond the awful color choice):

RectangleSub *rs = [Rectangle rectangleOfColor:[NSColor puceColor]];

Whereas the (id) return type would not warn in the second case.

Note that I also switched declared newInstance to be explicitly of type Rectangle*. This is more better, too, in that within the context of that method, newInstance can only be safely treated as a Rectangle*.

like image 133
bbum Avatar answered Nov 18 '22 06:11

bbum


+ (Rectangle *)rectangleOfColor:(NSColor *) color
{
    self = [[Rectangle alloc] init]; // BAD
    [self setColor:color];
    return self;
}

In class method self refers to the class, not a instance object of it.


+ (id)rectangleOfColor:(NSColor *)color
{
    id newInstance = [[Rectangle alloc] init]; // GOOD
    [newInstance setColor:color];
    return newInstance;
}

If Rectangle would be subclassed (MyFancyRectangle), this still would return a plain Rectangle object, while

+ (id)rectangleOfColor:(NSColor *)color
{
    id newInstance = [[self alloc] init]; // EXCELLENT
    [newInstance setColor:color];
    return newInstance;
}

would return a MyFancyReactangle if called like MyFancyReactangle *r = [MyFancyReactangle rectangleOfColor:[UIColor redColor]], as [self alloc]is called on the sublass. Note, that here self is again called on the class, as +alloc is a class method.

For the same reason all init… and convenient creator methods should return id. It allows subclasses to return subclass'ed objects without the compiler going mad.

like image 24
vikingosegundo Avatar answered Nov 18 '22 08:11

vikingosegundo


In the first case, you assign the self pointer (which should point to the Rectangle class object) to an instance of Rectangle. This is absolutely incorrect.

In the second, you hard code a class to instantiate - Rectangle in this case.

In the third, you allow the class's identity to determine the class of the instance, rather than specifying it explicitly in code. Then, if your Dodecahedron class needs to use this same code, it won't require changing the class name.

like image 6
Carl Veazey Avatar answered Nov 18 '22 08:11

Carl Veazey