Type Encodings
From number stations and numerology to hieroglyphs and hobo codes, there is something truly fascinating about finding meaning that hides in plain sight. Though hidden messages in and of themselves are rarely useful or particularly interesting, it’s the thrill of the hunt that piques our deepest curiosities.
It is in this spirit that we take a look at Objective-C Type Encodings in this week’s edition of NSHipster.
Last week, in a discussion about NSValue
, there was mention of +value
, whose second parameter should be created with the Objective-C @encode()
compiler directive.
@encode
, one of the @
Compiler Directives, returns a C string that encodes the internal representation of a given type (e.g., @encode(int)
→ i
), similar to the ANSI C typeof
operator. Apple’s Objective-C runtime uses type encodings internally to help facilitate message dispatching.
Here’s a rundown of all of the different Objective-C Type Encodings:
Code | Meaning |
---|---|
c |
A char
|
i |
An int
|
s |
A short
|
l |
A long l is treated as a 32-bit quantity on 64-bit programs. |
q |
A long long
|
C |
An unsigned char
|
I |
An unsigned int
|
S |
An unsigned short
|
L |
An unsigned long
|
Q |
An unsigned long long
|
f |
A float
|
d |
A double
|
B |
A C++ bool or a C99 _Bool
|
v |
A void
|
* |
A character string (char * ) |
@ |
An object (whether statically typed or typed id ) |
# |
A class object (Class ) |
: |
A method selector (SEL ) |
[array type] | An array |
{name=type...} | A structure |
(name=type...) | A union |
b num |
A bit field of num bits |
^ type |
A pointer to type |
? |
An unknown type (among other things, this code is used for function pointers) |
Charts are fine, but experimenting in code is even better:
NSLog(@"int : %s", @encode(int));
NSLog(@"float : %s", @encode(float));
NSLog(@"float * : %s", @encode(float*));
NSLog(@"char : %s", @encode(char));
NSLog(@"char * : %s", @encode(char *));
NSLog(@"BOOL : %s", @encode(BOOL));
NSLog(@"void : %s", @encode(void));
NSLog(@"void * : %s", @encode(void *));
NSLog(@"NSObject * : %s", @encode(NSObject *));
NSLog(@"NSObject : %s", @encode(NSObject));
NSLog(@"[NSObject] : %s", @encode(typeof([NSObject class])));
NSLog(@"NSError ** : %s", @encode(typeof(NSError **)));
int int Array[5] = {1, 2, 3, 4, 5};
NSLog(@"int[] : %s", @encode(typeof(int Array)));
float float Array[3] = {0.1f, 0.2f, 0.3f};
NSLog(@"float[] : %s", @encode(typeof(float Array)));
typedef struct _struct {
short a;
long long b;
unsigned long long c;
} Struct;
NSLog(@"struct : %s", @encode(typeof(Struct)));
Result:
Type | Encoding |
---|---|
int |
i |
float |
f |
float * |
^f |
char |
c |
char * |
* |
BOOL |
c |
void |
v |
void * |
^v |
NSObject * |
@ |
NSObject |
# |
[NSObject] |
{NSObject=#} |
NSError ** |
^@ |
int[] |
[5i] |
float[] |
[3f] |
struct |
{_struct=sq |
There are some interesting takeaways from this:
- Whereas the standard encoding for pointers is a preceding
^
,char *
gets its own code:*
. This makes sense conceptually, as C strings are thought to be entities in and of themselves, rather than a pointer to something else. -
BOOL
isc
, rather thani
, as one might expect. Reason being,char
is smaller than anint
, and when Objective-C was originally designed in the 80’s, bits (much like the dollar) were more valuable than they are today.BOOL
is specifically asigned char
(even if-funsigned-char
is set), to ensure a consistent type between compilers, sincechar
could be eithersigned
orunsigned
. - Passing
NSObject
directly yields#
. However, passing[NSObject class]
yields a struct namedNSObject
with a single class field:isa
, whichNSObject
instances have to signify their type.
Method Encodings
As mentioned in Apple’s “Objective-C Runtime Programming Guide”, there are a handful of type encodings that are used internally, but cannot be returned with @encode
.
These are the type qualifiers for methods declared in a protocol:
Code | Meaning |
---|---|
r |
const |
n |
in |
N |
inout |
o |
out |
O |
bycopy |
R |
byref |
V |
oneway |
For anyone familiar with NSDistantObject, you’ll doubtless recognize these as a vestige of Distributed Objects.
Although DO has fallen out of fashion in the age of iOS, it was an interprocess messaging protocol used between Cocoa applications–even running on different machines on the network. Under these constraints, there were benefits to be had from the additional context.
For example, parameters in distributed object messages were passed as proxies by default. In situations where proxying would be unnecessarily inefficient, the bycopy
qualifier could be added to make sure a full copy of the object was sent. Also by default, parameters were inout
, signifying that objects needed to be sent back and forth when sending the message. By specifying a parameter as in
or out
instead, the application could avoid the round-trip overhead.
So what do we gain from our newfound understanding of Objective-C Type Encodings? Honestly, not that much (unless you’re doing any crazy metaprogramming).
But as we said from the very outset, there is wisdom in the pursuit of deciphering secret messages.
Looking at type encodings reveals details about Objective-C runtime internals, which is a noble pursuit in and of itself. Going further down the rabbit hole, and we come to the secret history of Distributed Objects, and the obscure parameter qualifiers that still linger around to this day.