Search code examples
objective-cprotocolsintrospection

Introspect parameter of type: id to decide whether it is a class or a protocol


I have the following method:

-(void)SomeMethod:(id)classOrProtocol;

It will be called like this:

[self someMethod:@protocol(SomeProtocol)];

Or

[self someMethod:[SomeClass class]];

Within the method body I need to decide if |classOrProtocol| is:

Any Class(Class) OR Any Protocol(Protocol) OR Anything else

[[classOrProtocol class] isKindOfClass: [Protocol class]]

Results in a (build)error:

Receiver 'Protocol' is a forward class and corresponding @interface may not exist

So how can I tell a Protocol from a Class from anything else?


Solution

  • In Objective-C 2 (i.e. unless you use 32 bit runtime on OS X) Protocol is defined to be just a forward class, see /usr/include/objc/runtime.h. The real interface is nowhere declared. You can try to include /usr/inlcude/objc/Protocol.h by saying

    #import <objc/Protocol.h>
    

    but as is written there, no method is publicly supported for an instance of Protocol. The only accepted way to deal with Protocol instances is to use runtime functions, given in Objective-C Runtime Reference. It's not even publicly defined whether Protocol is a subclass of anything, and it's not even stated that it implements NSObject protocol. So you can't call any method on it.

    Of course you can use the source code of the runtime to see what's going on. Protocol inherits from Object (which is a remnant from pre-OpenStep NeXTSTep), not from NSObject. So you can't use the familiar methods for NSObject-derived objects, including Class of NSObject-derived objects. See the opensourced implementations of Protocol.h and Protocol.m. As you see there, the class Protocol itself doesn't do anything, because every method just casts self to protocol_t and calls a function. In fact, as can be seen from the function _read_images and others in objc-runtime-new.mm, the isa pointer of a Protocol object is set by hand when the executable and libraries are loaded, and never used.

    So, don't try to inspect whether an id is a Protocol or not.

    If you really need to do this, you can use

    id foo=...;
    if(foo->isa==class_getClass("Protocol")){
        ...
    }
    

    But, seriously, don't do it.