AnyObject
When is AnyObject not AnyObject? When itâs a protocol type.
Swift has a type called AnyObject that represents a single reference-counted object, with no available operations.1 This doesnât sound very useful, but sometimes youâre just using the object for its lifetime (a sort of dynamic RAII), and other times youâre planning to downcast it to a concrete type.
AnyObject can also be used as a generic constraint. If you use T: AnyObject
, youâre guaranteed that T will have that single-object-reference representation. This allows you to have weak
and unowned
references to T, as you might expect.
You can also use AnyObject as a constraint on protocols: protocol MyDelegate: AnyObject
. Now the implementers are known to have reference semantics, and with T: MyDelegate
you can have weak references to T, as before. You can even have weak references to any MyDelegate
, allowing swapping between delegates of different types.
What you might run into, though, is that any MyDelegate
is not itself AnyObject.
(What?)
If you try to use any MyDelegate
as a T: AnyObject
, youâll find the compiler is unhappy with you. Even though every concrete MyDelegate type is a valid AnyObject type, any MyDelegate
itself is not. Why not? Because it carries more information than just a single object reference: it also has a âwitness tableâ pointer, the run-time representation of a protocol conformance. Thatâs how protocol types (any
types) work in Swift: they have the normal value, stored in-line or out-of-line depending on size, plus the additional witness table thatâs full of method pointers, basically. When you call a protocol method through an any
type, the code at run time will look in the table that was given, and pull out the appropriate implementation of that method, then call it using the value part as self
.2
But wait, Objective-C never had this problem! The id <MyDelegate>
type doesnât take up more than a single-object-reference to store! But thatâs because ObjC protocols arenât represented as tables of methods; theyâre just promises that the implementing class has methods with particular names. So the âtableâ for a protocol is the same as the âtableâ for all of a classâs methodsâ¦at the cost of a single namespace for method names (selectors) and a bit of extra overhead on app launch and on method calls. And thatâs stored in the type, and every object knows its type, so thereâs no secondary pointer attached to the value.
(Was this a good tradeoff? A big question, with a lot more in the balance than just the representation of protocol types. Another time, perhaps.)
So thatâs why Swift class-constrained protocol types are just a bit less convenient than ObjC ones. You can still convert values to AnyObject, you can still reference them weakly or unownèdly, you can still trust that they have a fixed cost to copy and move. And you can still use the protocol as a constraint and get all the same effects. But you canât mix the two, because any MyDelegate
does not itself have an AnyObject
representation.
-
This is a lie on Apple OSs, but I encourage everyone to act as if it were true. ↩︎
-
This is also why methods defined in a protocol extension are statically resolved: theyâre not in the table. And separately-compiled modules means it may not be possible to put them in the table. So thereâs just one rule, and thatâs that only the requirements of the protocol are dynamically dispatched. ↩︎