Return "kindof class" to support existing Obj-C code during migration to Swift?

I am starting to convert an Obj-C project to Swift and am running into an issue when trying to implement a Swift version of + (nullable __kindof Foo) getFoo;*, which can return an instance of Foo or any subclass of Foo.

If I were working entirely in Swift, I would use something like class func getFoo<T: Foo>() -> T to get the same behavior, however the compiler complains about this ("method cannot be marked @objc because it has generic parameters").

At this point, it forces me to cast the method result every place it is used, i.e., someFoo = (SonOfFoo)SonOfFoo.getFoo*, so I was wondering if there is a language or other means of getting similar behavior.

[Your example is kinda (hey hey!) short on details, so I’ve made a bunch of guess here. Let me know if I got anything wrong.]

On the Swift side you have this:

class Foo: NSObject {
    @objc class func getFoo() -> Foo {
        return Foo()
    }
}

class SonOfFoo: Foo {
    override class func getFoo() -> Foo {
        return SonOfFoo()
    }
}

And on the Objective-C side you have this:

Foo * f = [Foo getFoo];
… use `f` …
SonOfFoo * sf = [SonOfFoo getFoo];
… use `sf` …

Your issue is that line 3 triggers a warning Incompatible pointer types initializing 'SonOfFoo *' with an expression of type 'Foo * _Nonnull'.

Is that right?

If so, one option would be to add an Objective-C category that vends a method with the signature you need:

@interface Foo (ObjCHappiness)

+ (__kindof Foo *)getFoo2;

@end

@implementation Foo (Hack)

+ (__kindof Foo *)getFoo2 {
    return [self getFoo];
}

@end

Your Objective-C code can then use +getFoo2 rather than +getFoo and not get this warning.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes

Sorry, I was being generic (!) because there are a number of cases where methods return a subclass. Typically they are category and protocol class methods where the behavior is exactly the same but the returned class type varies.

Here’s a concrete example:

  • (NSArray<__kindof NSManagedObject*>) getAllOfMeInContext:(NSManagedObjectContext context);

Callers could then use it without having to cast to a specific subclass:

NSArray<Foo*>* allFoos = [Foo getAllOfMeInContext:context];

In Swift I could get the same behavior:

class func getAllOfMe(in context: NSManagedObjectContext) -> [T] where T: NSManagedObject

let allFoos = Foo.getAllOfMe(in: context) // == [Foo]

If I try to get the “kindof” behavior in the Swift version replacing the Obj-C method, the compiler complains once about generics (cannot use generics with @objc). If I just return, i.e., [NSManagedObject], then I get a ton of incompatible pointer type warnings.

The migration process will proceed at a snail’s pace relative to other work, so there will be a mix of Swift and Obj-C for some time. I’m trying to avoid two things:

  1. The need to go through and cast result types that didn’t need to be cast before; and

  2. Keeping separate Obj-C and Swift versions of these methods to satisfy #1.

I could migrate as much as possible to Swift and bite the bullet on #2 for now, but I’m trying to avoid the case where something gets fixed in one method and not the other.

I believe you can achieve this now by using the some keyword.

@objc class func getFoo() -> some Foo {
    return Foo()
}

Thanks, Max.

I don't remember offhand how I finally ended up handling it since it's been a while now. I did eventually get everything migrated over to Swift, though, so I probably just dealt with the in-progress mess.