Search code examples
objective-cgenericscastingcovariance

Can I use __kindof with protocols in Objective-C?


I know I can make use of Objective-C's lightweight generics using the __kindof keyword, e.g.

NSArray<__kindof BaseClass*> *myArray;

This would remove any warnings for assigning any object from the array to a derived class.

However, instead of BaseClass, I have BaseProtocol, e.g. all my classes in question will conform the BaseProtocol, regardless of their base class. I want to use lightweight generics to dictate "my array consists of elements conforming to BaseProtocol, but they can be any class".

For example in C#, I can say: List<IMyInterface> which would mean that the list consists of elements that implement the IMyInterface interface (I know C# has strong generics and Objective-C has only lightweight generics that doesn't prevent compilation, but you get the idea).

Is there any way to achieve this functionality on Objective-C?

E.g. I want to write

NSArray<__kindof id<MyProtocol>> //compiles, but as the generic argument is "id", it accepts any object, including invalid ones

or

NSArray<id<__kindof MyProtocol>> //doesn't compile

Is this possible?

UPDATE:

Here is a complete self-contained code:

@protocol MyProtocol

@end

@interface MyClass : NSObject<MyProtocol>

@end

@implementation MyClass

@end

@interface AnotherClass : NSObject

@end

@implementation AnotherClass

@end



NSMutableArray<__kindof id<MyProtocol>> *myArray;

void test(){
    MyClass *myClassInstance = [[MyClass alloc] init];
    AnotherClass *anotherClassInstance = [[AnotherClass alloc] init];

    myArray = @[].mutableCopy;
    [myArray addObject:myClassInstance];
    [myArray addObject:anotherClassInstance]; //i get warning. good.

    MyClass *returnedInstance = myArray[0];
    AnotherClass *anotherInstance = myArray[1]; //why don't I get a warning here?
}

Solution

  • This syntax is correct:

    NSArray <__kindof id <MyProtocol>> *array = ...
    

    You can also omit __kindof, too, and still enjoy lightweight generics. It will still warn you about adding objects of the wrong type even without that keyword. The __kindof is used if you want to pull an object out of that array and assign it to a subtype without a cast, but otherwise __kindof is not needed:

    NSArray <id <MyProtocol>> *array = ...
    

    Both of these patterns will warn you if you add an object of a specific type to the array, but that type doesn't conform to MyProtocol.

    What this won't warn you about is if you try to add an object of type id by itself. So avoid using unqualified id types within your code and you'll enjoy lightweight generics.

    If you're still not seeing the warnings, make sure you have -Wobjc-literal-conversion warning turned on. So, go back to your build settings for the first project and search for "literal" and you'll see the setting (called "Implicit Objective-C Literal Conversions").


    Consider this example:

    @protocol MyProtocol
    @end
    
    @interface Foo: NSObject <MyProtocol>
    @end
    
    @interface Bar: Foo
    @end
    
    @interface Baz: NSObject
    @end
    

    Then consider:

    Foo *foo = [[Foo alloc] init];
    Bar *bar = [[Bar alloc] init];
    Baz *baz = [[Baz alloc] init];
    id   qux = [[Baz alloc] init];
    
    NSArray <id <MyProtocol>> *array1;
    array1 = @[foo, bar, baz, qux];           // warning: object of type 'Baz *' is not compatible with array element type 'Foo *'
    

    Note, that warns us about baz, but not qux. So be wary of using id types.

    id <MyProtocol> object1 = array1[0];      // no warning, great
    

    So, that's using the protocol as a lightweight generic and it works as expected.

    The only reason you'd add __kindof is if you wanted to avoid this warning:

    Foo *foo1 = array1[0];                    // warning: initializing 'Foo *__strong' with an expression of incompatible type 'id<MyProtocol> _Nullable'
    

    In that case, you'd use __kindof:

    NSArray <__kindof id <MyProtocol>> *array2;
    array2 = @[foo, bar, baz];                // again, warning: object of type 'Baz *' is not compatible with array element type 'Foo *'
    
    id <MyProtocol> object2 = array2[0];      // no warning, great
    
    Foo *foo2 = array2[0];                    // no warning, great