GCD came along with 10.6 and made concurrent programming easy. ARC came along with 10.7 and let us mostly forget about this whole refcounting business.
But 10.7’s GCD was left behind in manual retain-release land. (XPC was too, but
GCD is our hero this time.) 10.8 fixed that oversight via a clever hack hidden
Behold, I bring you an object!
The magic happens in the interaction between two macros,
OS_OBJECT_DECL is used to declare the base object type of your refcounted C
library. It conceptually creates a new root class:
Once you’ve declared a root class using
OS_OBJECT_DECL, you use
OS_OBJECT_DECL_SUBCLASS to declare new subclasses:
OS_OBJECT_DECL_SUBCLASS(dispatch_queue, dispatch_object); OS_OBJECT_DECL_SUBCLASS(dispatch_source, dispatch_object);
And magically, you now have types
As far as casts are concerned, these new types behave just like
NSNumber. If you declare variables like so:
NSObject *o; NSNumber *n; NSString *s;
The compiler will allow you to implicitly upcast without complaint:
/* hunky dory */ o = n; o = s;
but not down or crosswise:
dispatch_cast.m:13:4: warning: incompatible pointer types assigning to 'NSNumber *__strong' from 'NSObject *__strong' [-Wincompatible-pointer-types] n = o; ^ ~ dispatch_cast.m:14:4: warning: incompatible pointer types assigning to 'NSNumber *__strong' from 'NSString *__strong' [-Wincompatible-pointer-types] n = s; ^ ~
Similarly, with these declarations:
dispatch_object_t o; dispatch_queue_t q; dispatch_source_t s;
This is fine:
/* hunky dory */ o = q; o = s;
But this is not:
q = o; q = s;
The error messages hint at how this is implemented:
dispatch_cast.m:27:4: warning: incompatible pointer types assigning to '__strong dispatch_queue_t' (aka 'NSObject<OS_dispatch_queue> *__strong') from '__strong dispatch_object_t' (aka 'NSObject<OS_dispatch_object> *__strong') [-Wincompatible-pointer-types] q = o; ^ ~ dispatch_cast.m:28:4: warning: incompatible pointer types assigning to '__strong dispatch_queue_t' (aka 'NSObject<OS_dispatch_queue> *__strong') from '__strong dispatch_source_t' (aka 'NSObject<OS_dispatch_source> *__strong') [-Wincompatible-pointer-types] q = s; ^ ~
OSObject Is Protocols!
And that’s the trick, you see. There aren’t any classes, just protocols.
Because protocols can be declared as conforming to other protocols, we have a
protocol hierarchy parallel to our class hierarchy. By using a
protocol-qualified type –
NSObject<OS_dispatch_queue> * meaning, “Any
NSObject, so long as it conforms to
OS_dispatch_queue” – we can make our
hierarchy concrete in terms of which OS objects can be pointed at by which
NSObject and not
id? Because ARC needs to be able to use
retain/release/autorelease, and NSObject provides a convenient declaration of
those and other methods.
Or Is It?
Of course, there would have to be more to this OSObject thing than just
protocols for ARC to work: whatever type-level hackery you might perpetrate,
the message send
[pointer retain] is only going to work if the whole
Objective-C message send machinery can use what’s at
*pointer as an Obj-C
Consequently, things look a lot different from inside libdispatch. There are covert class interfaces and corresponding implementations that go along with the public protocols.
A shame you can’t just sprinkle a few macros over a C library that uses refcounting and have it work automagically with ARC. Now, there’s a thought…