Search code examples
objective-cobjective-c-protocol

Checking protocol conformance when using factory methods in Objective-C


I have recently been learning how to use protocols in Objective-C (using Apple's official guide) and I've been having trouble understanding what seems to me to be an inconsistency. In the documentation it is stated that -

By specifying the required protocol conformance on the property, you’ll get a compiler warning if you attempt to set the property to an object that doesn’t conform to the protocol, even though the basic property class type is generic.

So I test this out by creating a protocol called 'XYZFakeProtocol' and a class called 'XYZPerson' that does not conform to this protocol. I then attempt to initialise a generic class variable which is expected to conform to XYZFakeProtocol as follows -

id <XYZFakeProtocol> speakingPerson = [[XYZPerson alloc] init];

As expected, XCode flags up the error -

Initializing '__strong id<XYZFakeProtocol>' with an expression of incompatible type 'XYZPerson *'

However, when I do the same thing but instead use a factory method as opposed to manually allocating and initialising an instance, the error does not come up. The code I used, with the factory method being 'person:' -

id <XYZFakeProtocol> speakingPerson = [XYZPerson person];

No error is flagged, and, especially problematically, no compiler error also appears when I call a method specified in the protocol, even when that method is not actually in the non-conforming class - this causes my program to crash.

So is this a problem with Xcode, or is this the expected and correct outcome of using a factory method, and, if so, could the reasoning behind this be explained to me so that I can understand how best to avoid it when I'm coding a real application?

For reference, Xcode correctly flags an error that the class does not conform to the protocol if I create an XYZPerson object and assign it to an XYZPerson variable, and then assign that variable to the generic type variable, regardless of whether the instance was created using a factory method or initialised manually -

XYZPerson *helloPerson = [XYZPerson person];
XYZPerson *helloPerson2 = [[XYZPerson alloc] init];
id <XYZFakeProtocol> speakingPerson = helloPerson;
speakingPerson = helloPerson2;

Thanks.


Solution

  • It's safe to assume you've declared as:

    + (id)person;
    

    Change that to:

    + (instancetype)person;
    

    id represents any type so the compiler doesn't know whether the object returned will implement the protocol or not. instancetype means an instance of that type so the compiler does know.

    instancetype is a relatively new addition and hence the compiler is happy to adduce it for init methods. It's however not willing to do so for factory methods.