Clang: promoting warning for type-casting like id<protocol> to class<Protocol>? - objective-c

I'm doing some code refactoring but facing a problem that clang has no any warning for this situation:
#protocol CommonProtocol <NSObject>
- (void)common;
#end
=========
#interface Foo : NSObject <CommonProtocol>
- (void)foo;
#end
=========
#interface Bar : NSObject <CommonProtocol>
#end
=========
static id<CommonProtocol> getObject(void) {
return [Bar new];
}
Foo *casting = getObject(); /// ⚠️ there is no warning for this
[casting common];
[casting foo]; /// 💥 crash because the casting object do not respond to '-foo'
I think type 'Foo *' and 'id<CommondProtocol>' are not interchangeable and looking a way to promote this warning. Any compile flag to achieve this?
NOTE: I have try '-Weverything' and it still present no warning for conversion.

This is a natural consequence of the weak type system of Objective-C, where id can be virtually downcasted to any kind of object. Specializing id with a given protocol doesn't help much in this direction, this is why you can easily run into unexpected situations, like the current one.
If you want to force the casting, one solution would be to change the signature of getObject to return NSObject instead of id:
static NSObject<CommonProtocol> *getObject(void) {
return [Bar new];
}
With this setup in place the naive Foo *casting = getObject(); will generate a clear warning:
Incompatible pointer types initializing 'Foo *' with an expression of type 'NSObject *'
, and the person writing the code will have to explicitly cast:
Foo *casting = (Foo *)getObject();

Related

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?
}
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

Pros and cons of using "id" as the return type of a custom "init" method, instead of a pointer to that class?

Assume the following Objective-C class:
#interface Appliance : NSObject
{
NSString *productName;
int voltage;
}
#end
What are the pros and cons of implementing init method A instead of B?
A) -(id)initWithName:(NSString *)name;
B) -(Appliance *)initWithName:(NSString *)name;
I see they both work in XCode, i.e. they both will result in a valid Appliance instance. "A" seems to be the standard among books I've read and codebases I've looked at, and I'm wondering why this is.
Point in fact, for quite some time the best practice return type from a class initializer (in Objective-C) is instancetype instead of id.
Oh, reopen. :-)
Indeed, you did not ask for the difference id vs. instancetype. And for -init… the answer to this non-asked Q would be easy: There is no difference, because the compiler converts id to instancetype silently.
You asked for id vs. CustomClass*. And you get a completely different answer from me: With CustomClass* a subclass had to cast the result of the superclass' designated initializer. Let's have an example:
#interface BaseClass : NSObject
- (BaseClass*)initWithWhatever; // Typed to class, designated initializer
#end
#implementation BaseClass
- (BaseClass*)initWithWhatever // Typed to class
{
self = [super init]; // What's the return type of -init (NSObject)?
…
}
#end
#interface Subclass : BaseClass
// First problem: I like it to announce in the interface, that a class overwrites
// a method of the base class. Doing so I would have to change the return type. Ugly.
// If I do not redeclare -initWithWhatever it is inherited from BaseClass, still
// having BaseClass* as the return type. Is that the truth? Really?
// However, I do not overwrite it here, but have a new initializer.
- (Subclass*)initWithSomethingElse;
#end
#implementation Subclass
- (Subclass*)initWithSomethingElse
{
// Second Problem:
// First, I have to execute the superclass' designated initializer
self = [super initWithWhatever];
// Wait a minute!
// self is a reference to Subclass. The return value of -initWithWhatever has the type
// BaseClass*. So I assign a reference of the base class to a reference of the subclass:
// Compiler error, false positive. The code is correct.
// So I would have to cast. Ugly, ugly, ugly.
#end
…
// Third problem:
Subclass *object = [[Subclass alloc] initWithWhatever];
// Typing -initWithWhatever to BaseClass* would lead to a compiler error here again.
// Compiler error, false positive. The code is correct.
To make the long story short: Without a mass of castings it would be impossible to type initializers to the concrete class.

why objective-c does not support same method name in difference class?

Here is a code snippet from Learning objective-c 2.0
Full code:
ClassWithFloat.h
#import <Foundation/Foundation.h>
#interface ClassWithFloat : NSObject
{
float value;
}
-(void)setValue:(float)aValue;
#end
ClassWithFloat.m
#import "ClassWithFloat.h"
#implementation ClassWithFloat
-(void)setValue:(float)aValue
{
value = aValue;
}
#end
ClassWithInt.h
#import <Foundation/Foundation.h>
#interface ClassWithInt : NSObject
{
int value;
}
-(void)setValue:(int)aValue;
#end
ClassWithInt.m
#import "ClassWithInt.h"
#implementation ClassWithInt
-(void)setValue:(int)aValue
{
value = aValue;
}
#end
main.m:
#import <Foundation/Foundation.h>
#import "ClassWithFloat.h"
#import "ClassWithInt.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
id number = [[ClassWithInt alloc] init];
[number setValue:3];
}
return 0;
}
failed to compile, after changing to ClassWithInt* number it works.
Error message:
/Users/jcyangzh/src/oc/SameName/SameName/main.m:17:9: Multiple methods named 'setValue:' found with mismatched result, parameter type or attributes
But since objective-c is somehow a dynamic programming language, the message call will be translated to native C method call.
obj_msgSend(number, #selector(setValue:), 3)
the obj_msgSend method find the class structure for the number object by isa variable. Which should make no difference between id or ClassWithInt type.
Why objective-c compiler could not recognize the right method?
Note: I am asking this question, because having same method name, but different argument type for different class is reasonable to me. But it seems that it is not possible either because the compiler limitation or the language design (do not supporting method overloading etc).
The problem really is that your object is only typed as id within the lexical scope.
The compiler doesn't know which method of the same name/selector to use.
You have multiple classes that have that selector but with different signatures because their arguments are different types.
You should avoid id in this case
Or typecast your object in the message send brackets to tell the compiler what class's method to use
Or
Bracket the same message call repeatedly in a sequence of if ([obj isKindOf:
checks. (Crazy here)
Or
Best take a hint from NSNumber class on good method naming conventions and do something like setFloatValue: and setIntValue: which is more readable and clear and helps the compiler a bit.
But any time you have and id type only, you need to be checking if the object isKindOf: or you are asking for trouble.
It is very very bad to have methods with same name but different signatures. (It is documented somewhere but I can't find now)
The calling conversion between calling setValue:(float) is different to setValue:(int), compiler have to generate different binary code.
As you said, it end up with something like
obj_msgSend(number, #selector(setValue:), 3)
but they are different
obj_msgSend(number, #selector(setValue:), (int)3)
obj_msgSend(number, #selector(setValue:), (float)3.0f)
Compiler have to decide at compile-time to generate the which version. Because the calling conversion between pass parameter with int type and float type are different.
Given code
ClassWithInt *number = [[ClassWithInt alloc] init];
[number setValue:3];
Compile know it need to generate the version with int with the help of type information.
but without type information
id number = [[ClassWithInt alloc] init];
[number setValue:3]; // is this takes int or float? if it is float then 3 need to be convert to float value first
There are two possible way to call it. Compiler can't figure it out without help. Hence the error message.

Why does adding a protocol (to id) cause semantic issues?

Let's say I have a trivial class like this:
#interface ABPair : NSObject
#property id key;
#property id value;
- (void) printSize;
#end
#implementation ABPair
- (void) printSize {
NSLog(#"the size of your key is: %#", NSStringFromSize([self.key sizeWithAttributes: nil]));
}
#end
This compiles with no warnings (in Xcode 5), and runs successfully, and prints a reasonable value.
However, if I made this one change:
#property id<NSCopying> key;
then I get two compiler errors:
ARC Semantic Issue: No known instance method for selector 'sizeWithAttributes:'
Semantic Issue: Passing 'id' to parameter of incompatible type 'NSSize' (aka 'struct CGSize')
Why is the compiler able to identify the proper method (on NSString) when I provide no information at all about the type, but unable to identify the method when I say that the object must be of a protocol to which that class conforms?
id key declares a "generic" Objective-C variable. It can point to any object,
and the compiler accepts any messages sent to it.
If you compile with ARC, it is only required that the message signature is known
to the compiler at all (from any class).
id<myprotocol> key specifically declares a pointer to an object conforming to that protocol.
The compiler accepts only messages from the <myprotocol> protocol (or other protocols that <myprotocol> inherits from).

What's the most robust and readable way of ensuring objects conform to a interface/protocol in Objective C?

I'm trying code to an interface (or a protocol in Objective C terminology), not an implementation.
It's critical that we check objects conform to protocol before calling methods on them to prevent crashes.
Three Ways
In compiler
At runtime
Both
Best Solution... Surely Number 1?
I thought the best way would be in the compiler:
Warnings ahoy if you screw up
Eliminates conformsToProtocol:/respondsToSelector: boilerplate
At runtime it's too late if you made a mistake - the best you can do is not execute the code/show an error
But I see a lot of code that's doing it at runtime. Why?
Is it a readability issue - needing id <Protocol> everywhere?
My Question
What's the most robust and readable way of ensuring objects conform to a interface/protocol?
Code
1. Checking In Compiler
#interface ReportController : NSObject {
id <ReportGenerator> generator;
id <ReportSender> sender;
id report;
}
#implementation ReportController
-(id)initWithReportGenerator:(id <ReportGenerator>)generator_
reportSender:(id <ReportSender>)sender_ {
// Usual init stuff
generator = generator_;
sender = sender_;
return self;
}
-(void)generateAndSend {
report = [generator generate];
[sender sendReport:report];
}
#end
2. Checking At Runtime
#interface ReportController : NSObject {
id generator;
id sender;
id report;
}
#implementation ReportController
-(id)initWithReportGenerator:(id)generator_
reportSender:(id)sender_ {
// Usual init stuff
generator = generator_;
sender = sender_;
return self;
}
-(void)generateAndSend {
if ([generator conformsToProtocol:#protocol(ReportGenerator)] &&
[sender conformsToProtocol:#protocol(ReportSender)]) {
report = [generator generate];
[sender sendReport:report];
} else {
[NSException raise:NSInternalInconsistencyException format:#"Objects didn't respond to protocols..."];
}
}
#end
You should use both. Consider e.g.:
#protocol Proto
- (void)someFunction;
#end
#interface C : NSObject
- (void)proto:(id<Proto>)p;
#end
// ...
NSString *s = #"moo";
id i = s;
C *c = [[C alloc] init];
[c proto:s]; // warns
[c proto:i]; // doesn't warn
Objective-C and Cocoa are too dynamic to generally check such things at compile time (NSProxy standins, classes dynamically adding methods and protocols, ...).
It is nice to catch as many of such errors at compile-time as possible, but that alone is not sufficient.
As long as you don't use plain id as the type, the compiler will at least warn you if you make a mistake at compile time. So you should be fine with your code example #1.
Of course, sometimes you might be forced to work with an id object that you get from a subsystem that is not under your control. In such cases you can cast the object back to the type you think it has (e.g. id <ReportGenerator>), but you are usually better off if you perform a runtime check first. Better be safe than sorry...
On a final note: If your protocol has optional parts (declared with the #optional keyword), then for those parts you will obviously be able to do runtime checks only. The #required keyword mentioned by apurv is necessary only if you want to be explicit in your protocol declaration (a protocol's parts are required by default), or if you mix optional and required parts.
You should create methods with #required type in protocol.
So whatever class wants to take a contract with this protocol, will must have to implement those methods.
It will definitely make sure that the required methods are available at compile time only.