Type Encodings Explained
July, 2020
In this article we’re going to look into Objective-C’s type encodings, and then we’ll then build a small convenience class to parse these encodings so we can use them more easily.
Type encodings are compact string codes that describe the details of a type, and are compiled into the metadata of your app. We touched on them in Inspecting Objective-C Properties, where they appeared in the type attribute of each property.
Every type has an encoding, and although they are always represented as C strings, most of them are just a single character. For example, int
types are represented by i, and double
is d.
The other scalar types are just as straightforward and don’t need much explanation, but you can find the full list in Apple’s Runtime Programming Guide.
Type encodings are also available for Objective-C specific types, such as:
id
pointers encode as @Class
encodes as #SEL
(selectors) encode as :
Some encodings are more than just a single character and include extra information.
More complex encodings
Object types
While plain id
types are represented as @, an object of a specific class like NSString
has the class name appended in quotes: @"NSString".
Even protocols are included in the encoding if the type is declared as such. Something like id<NSCopying>
encodes to @"<NSCopying>".
Block types
Blocks are treated like objects, but their type encodings do not include any information about the block parameters or return type. Every block type is represented as @?, which combines the encodings for “object” and “function pointer”.
Getting the type encodings for the parameters of a block is quite a bit more complicated, but it is possible. The internal details of blocks are documented in the Block Implementation compiler spec, and you can find some example code which will extract them for you.
Compound types
Structs and unions are encoded with the types of all of their member fields. For example, CGSize
, which is defined as:
struct CGSize {
CGFloat width;
CGFloat height;
};
Has a type encoding of {CGSize=dd}.
Note that in 64-bit apps,
CGFloat
is defined asdouble
(notfloat
) which is why the two fields have a type of d.
This works even for nested structs like CGRect
, which is a combination of a CGPoint
origin and a CGSize
. The type encoding for CGRect
is:
{CGRect={CGPoint=dd}{CGSize=dd}}
Depending on where the type encoding comes from, it can sometimes even include the member names as well:
{CGRect="origin"{CGPoint="x"d"y"d}"size"{CGSize="width"d"height"d}}
Other C types
The remaining types in the list like void
, C strings, C arrays, bitfields, function pointers and non-object pointers are rarely used in Objective-C, so we won’t go into more detail on them here.
Instead, lets take a look at what encodings are used for.
Where type encodings are used
In the Objective-C runtime, type encodings are returned by several functions for describing the types of:
- Properties (
property_getAttributes
) - Ivars (
ivar_getTypeEncoding
) - Method parameters and return types (
method_getTypeEncoding
)
They are also used by some Foundation classes like NSMethodSignature
, which wraps the method-related runtime functions. The NSValue
class uses an encoding to keep track of the type of value it is wrapping, which is why you need to initialise it with an objCType
parameter.
To get the encoding of any type, we use the @encode
compiler directive. For example:
NSLog(@"%s", @encode(CGSize));
// Prints: {CGSize=dd}
Note that we need to use a type here, not a variable. To get the encoding of a variable, wrap it in the
typeof()
operator first.
A small gotcha is that the @encode
directive can return slightly different encodings than the runtime does. This is why the encoding of a struct might include the member names: @encode
does not add them, but ivar_getTypeEncoding
will. Not a big problem, but it can make it more complicated to compare type encodings.
A wrapper for type encodings
Let’s create a convenience class for dealing with type encodings, so we don’t have to parse C strings every time.
Firstly, we can turn the type encodings table into an enum:
typedef NS_ENUM(char, EncodedType) {
TypeChar = 'c',
TypeInt = 'i',
TypeShort = 's',
TypeLong = 'l', // note: long encodes to 'q' on 64 bit
TypeLongLong = 'q',
TypeUnsignedChar = 'C',
TypeUnsignedInt = 'I',
TypeUnsignedShort = 'S',
TypeUnsignedLong = 'L',
TypeUnsignedLongLong = 'Q',
TypeFloat = 'f',
TypeDouble = 'd',
TypeBool = 'B', // note: BOOL encodes to 'c' on 64 bit
TypeVoid = 'v',
TypeCString = '*',
TypeObject = '@',
TypeClass = '#',
TypeSelector = ':',
TypeArray = '[',
TypeStruct = '{',
TypeUnion = '(',
TypeBitField = 'b',
TypePointer = '^',
TypeUnknown = '?',
};
We’ll just use the first character of the encoding, since it’s enough to tell us which type it is.
You might think “shouldn’t we use @encode
to get these instead of hardcoding them?”. Well yes, technically, but these haven’t changed in decades and are very unlikely to change in the future. Since hardcoding them makes our life easier, I’m prepared to compromise in this particular case 🙂
Now let’s define an interface with some properties of encodings that would be handy to have:
@interface TypeEncoding : NSObject
@property (nonatomic, readonly) EncodedType type;
/// The raw type encoding
@property (nonatomic, readonly, copy) NSString *encoding;
/// If the type is either an object, a Class type, or a block
@property (nonatomic, readonly) BOOL isObjectType;
/// If the type is float or double
@property (nonatomic, readonly) BOOL isFloatType;
/// If the type is a signed or unsigned integer of any size
@property (nonatomic, readonly) BOOL isIntType;
/// The class of the object type. Nil for anything except TypeObject.
@property (nonatomic, readonly, nullable) Class classType;
- (instancetype)initWithEncoding:(NSString *)encoding;
@end
We’ll implement this by extracting the EncodedType
straight off the start of the encoding string:
@implementation TypeEncoding
- (instancetype)initWithEncoding:(NSString *)encoding {
self = [super init];
if (self) {
_encoding = [encoding copy];
_type = [encoding characterAtIndex:0];
static const unichar TypeConst = 'r';
if (_type == TypeConst) {
// const C strings are encoded as "r*", skip the 'r'
_type = [encoding characterAtIndex:1];
}
}
return self;
}
We could set all the properties right in init
, but we’ll extract those into separate getters. The isObjectType
and isFloatType
methods just test if the EncodedType
matches certain values:
- (BOOL)isObjectType {
return _type == TypeObject || _type == TypeClass;
}
- (BOOL)isFloatType {
return _type == TypeFloat || _type == TypeDouble;
}
We could explicitly check for each of the eleven integer types (including BOOL
), but another way would be to put all the integer types in an array and test if it contains our value. Since EncodedType
is a character, the array of types just becomes a C string:
- (BOOL)isIntType {
static const char *integralTypes = "cislqCISLQB";
return strchr(integralTypes, _type) != NULL;
}
The classType
method needs a little string manipulation to extract the class name (if present) out of the type encoding. So an encoding like @"NSURL" will return NSURL
, and id
will default to NSObject
.
- (Class)classType {
if (_type == TypeObject
&& [_encoding hasPrefix:@"@\""] && [_encoding hasSuffix:@"\""]) {
NSRange range = NSMakeRange(2, _encoding.length - 3);
NSString *classStr = [_encoding substringWithRange:range];
return NSClassFromString(classStr) ?: NSObject.class;
}
return nil;
}
@end
Wrapping up
We could add some more functionality to our TypeEncoding
class, such as extracting protocol names or the names and member types of structs, but we can leave that until we need it.
One place this wrapper would already be useful is on ClassProperty
from the previous post. The encodeType
property can be changed from NSString
to TypeEncoding
and initialised as:
_encodeType = [[TypeEncoding alloc] initWithEncoding:attribDetail];
TypeEncoding
will also come in very handy for future posts, when we need to get and store the values of properties and ivars dynamically.
Useful links
- Inspecting Objective-C Properties post
- Apple’s Type Encodings reference
- Type Encoding implementation in LLVM
- Type Encodings on NSHipster
The source code from this post can be found on Github. If you spot a bug, please create an issue on there.
Any comments or questions about this post? ✉️ nick @ this domain.
— Nick Randall