Teo Sartori
Teo Sartori

Reputation: 1112

Obj-C factory method not exposed to Swift subclasses

More specifically imageNamed does not get exposed to Swift subclasses of NSImage even when all other convenience inits are inherited.

According to Apple's docs Objective-C factory methods "get mapped as convenience initializers in Swift."

Consequently the NSImage factory method

+ (NSImage *)imageNamed:(NSString *)name

gets mapped to Swift's

NSImage(named: "anything")

Also, in the Swift Programming Language book we see that the rules for subclass initializer inheritance are:

“Rule 1 If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.

Rule 2 If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.”

It thus follows that the subclass

class T : NSImage { }

would inherit all the superclass' initializers, both designated and convenience, yet the following will not work let I = T(named: "anything")

What am I missing?

Upvotes: 4

Views: 565

Answers (1)

Bartek Chlebek
Bartek Chlebek

Reputation: 1664

The reason for that seems to be that the named: initializer has a return type of NSImage

class NSImage : NSObject, NSCopying, NSCoding, NSSecureCoding, NSPasteboardReading, NSObjectProtocol, NSPasteboardWriting {

    /*All instance variables are private*/

    init?(named name: String) -> NSImage /* If this finds & creates the image, only name is saved when archived */

while other (not all) initializers have no return type

    init(size aSize: NSSize)
    init?(data: NSData) /* When archived, saves contents */
    init?(contentsOfFile fileName: String) /* When archived, saves contents */
    init?(contentsOfURL url: NSURL) /* When archived, saves contents */
    init?(byReferencingFile fileName: String) /* When archived, saves fileName */
    init(byReferencingURL url: NSURL) /* When archived, saves url, supports progressive loading */

Now because of that, if you have a class T that is a subclass of NSImage you would expect T(named:) to return an instance of T but if you do not provide your own implementation, the superclass implementation would be called and as we can see in the declaration, it returns an NSImage.

Why is it different than all other initializers? I don't know.
Perhaps the underlying implementation and caching mechanisms that NSImage uses lead to this inconsistency. But what's more important is that the +[NSImage imageNamed:] does not call any of the NSImage designated initializers.

Have a look at this example I cooked up witg Cocoa Touch's UIImage: https://gist.github.com/bartekchlebek/d61154add8525218ae3a

You can see there, that I create a subclass of UIImage called MyImage and I override all UIImage's initializers, place NSLogs in them and return nil.

In

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

I call -[MyImage imageNamed:] and notice, that none of the NSLog's placed in the designated initializers are printed. I also print the class of the instance created with -[MyImage imageNamed:] and you can see that it is not MyImage but UIImage (putting aside the fact that I return nil in all initializers ;) )


In conclusion, +[UIImage imageNamed:] does not call any of the UIImage's designated initializers, thus overriding them will not make +[MyImage imageNamed:] return an instance of MyImage. And I expect NSImage to behave the same way.

This subtlety causes Swift not to inherit the named: initializer, because swift requires convenience initializers to go through a designated initializer, while Objective-C did not require that.

Upvotes: 3

Related Questions