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 +valueWithBytes:objCType:, 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:

Objective-C Type Encodings
Code Meaning
c A char
i An int
s A short
l A longl 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
bnum 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 intArray[5] = {1, 2, 3, 4, 5};
NSLog(@"int[]      : %s", @encode(typeof(intArray)));

float floatArray[3] = {0.1f, 0.2f, 0.3f};
NSLog(@"float[]    : %s", @encode(typeof(floatArray)));

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=sqQ}

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 is c, rather than i, as one might expect. Reason being, char is smaller than an int, 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 a signed char (even if -funsigned-char is set), to ensure a consistent type between compilers, since char could be either signed or unsigned.
  • Passing NSObject directly yields #. However, passing [NSObject class] yields a struct named NSObject with a single class field: isa, which NSObject 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:

Objective-C Method Encodings
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.

NSMutableHipster

Questions? Corrections? Issues and pull requests are always welcome.

Written by Mattt
Mattt

Mattt (@mattt) is a writer and developer in Portland, Oregon.

Next Article

NSURLCache provides a composite in-memory and on-disk caching mechanism for URL requests to your application. As part of Foundation’s URL Loading System, any request loaded through NSURLConnection will be handled by NSURLCache.