When you pass @protocol(SomeProtocol)
as an argument to a method, can the resulting pointer be considered to have static storage duration?
Now considering that the protocol is defined at compile time, in a .h file, would that mean that it's pointer is the same throughout the entire lifetime of the program and can be safely dubbed, in a sense, static at run-time?
There's some pretty obvious confusion as to what a Protocol
pointer is, so I'll attempt to clear up any confusion regarding this.
Back in the olden days of ObjC, protocols simply did not exist. Because of this, when NeXT's API required multiple inheritance, they 'hacked' it into a language as a class of its own, and modified the compiler with special syntax to accept this.
It worked great, up until ObjC2 rolled around, and protocols were going to be added as an official language (and run-time API) feature, but that caused some backwards-compatibility problems, as we already had a Protocol
class that was defined by NeXT.
The solution that came about was to keep the Protocol class around, but deprecate it. Thus, we still technically have the Protocol
class lying around in the runtime, but none of the methods work in ObjC2 (this is actually the case with quite a few different older constructs, such as the forward::
selector, objc_msgSendv
, and a few other minor things, but I digress).
You should not (read can't) send messages to this class - while it is still technically an object, you should not expect it to conform to the same conventions (protocols?) of other objects that exist in your application.
The solution, then, is to use the functions in the runtime prefixed by protocol_
, such as:
protocol_getName()
instead of -name
.protocol_conformsToProtocol()
instead of -conformsTo:
etc.
Note that under the current implementation of the protocol class, these methods actually can still be called, and are simply a shim to their C-function counterparts.
With that being said, if all of your protocols are obtained by using objc_getProtocol
, they are, with the current version of the runtime, guaranteed to have 'static' storage, as we can see by the implementation:
/***********************************************************************
* objc_getProtocol
* Get a protocol by name, or return nil
* Locking: read-locks runtimeLock
**********************************************************************/
Protocol *objc_getProtocol(const char *name)
{
rwlock_read(&runtimeLock);
Protocol *result = (Protocol *)NXMapGet(protocols(), name);
rwlock_unlock_read(&runtimeLock);
return result;
}
No copies of the protocol are made, and every subsequent call will return the same pointer. This also applies to using the @protocol(name)
expression, which can essentially (although not exactly, there's some other compiler magic done) be considered a call to objc_getProtocol
.
Now - it is, however, theoretically possible for someone to intentionally create a copy of a protocol 'object', as the structure is quite simple, as outlined here:
struct protocol_t : objc_object {
const char *name;
struct protocol_list_t *protocols;
method_list_t *instanceMethods;
method_list_t *classMethods;
method_list_t *optionalInstanceMethods;
method_list_t *optionalClassMethods;
property_list_t *instanceProperties;
uint32_t size; // sizeof(protocol_t)
uint32_t flags;
const char **extendedMethodTypes;
};
(Note that this is a C++ structure that imitates an objective-c object, which can cause some difficulties if you try to emulate it yourself).
The important thing to note here is that very few issues would be caused by simply doing a memcpy
on the protocol pointer given to you by the runtime, and for that reason, you should always use protocol_isEqual()
for comparing protocols, which will check the fields of the protocol to ensure that they are actually equivalent, even if they have different pointers.
Simply treating a Protocol *
as static is completely fine, however, and is the proper way to reference a protocol in your code.
Note that, however, as with any runtime feature, this is all subject to change with another version of the API, Compiler, ABI, target architecture, and other things, so make sure to read up on the very latest information you can on the subject!