Objective C, Encoding and You

Creating an Objective C class at runtime

Note that as far as the runtime is concerned, there is zero difference between ivars and properties declared in @interface declarations and @implementation declarations. The only difference is visibility as far as the compiler is concerned.

class_addMethod(myClass, @selector(description), methodIMP, "@@:")
* @param types An array of characters that describe the types of the arguments to the method.
Method blah = class_getInstanceMethod([NSObject class], 
NSLog(@"%s", method_getTypeEncoding(blah));
- (BOOL)writeToURL:(NSURL *)url atomically:(BOOL)useAuxiliaryFile encoding:(NSStringEncoding)enc error:(NSError **)error;

As a side note, remember that the runtime also records strings for the method names and instance variable names so you also get foo, setFoo:, and _foo, and that since the language is dynamic, none of this can be dead-stripped by the linker. The Objective C runtime gets pretty verbose.

Since they don’t appear to be documented anywhere but the sources, here is the encoding characters for properties:

struct aStruct {
int foo;
long bar;
NSString *bam;
typedef struct {
int foo;
long bar;
NSString *bam;
} aStruct;
struct bStruct {
struct aStruct struct1;
struct aStruct struct2;
class aClass {
int foo;
long bar;
NSString *bam;
int doSomething(long foo);
{ObjCType=#{CppType={map<std::__1::basic_string<char>, std::__1::basic_string<char>, std::__1::less<std::__1::basic_string<char> >, std::__1::allocator<std::__1::pair<const std::__1::basic_string<char>, std::__1::basic_string<char> > > >={__tree<std::__1::__value_type<std::__1::basic_string<char>, std::__1::basic_string<char> >, std::__1::__map_value_compare<std::__1::basic_string<char>, std::__1::__value_type<std::__1::basic_string<char>, std::__1::basic_string<char> >, std::__1::less<std::__1::basic_string<char> >, true>, std::__1::allocator<std::__1::__value_type<std::__1::basic_string<char>, std::__1::basic_string<char> > > >=^{__tree_end_node<std::__1::__tree_node_base<void *> *>}{__compressed_pair<std::__1::__tree_end_node<std::__1::__tree_node_base<void *> *>, std::__1::allocator<std::__1::__tree_node<std::__1::__value_type<std::__1::basic_string<char>, std::__1::basic_string<char> >, void *> > >={__tree_end_node<std::__1::__tree_node_base<void *> *>=^{__tree_node_base<void *>}}}{__compressed_pair<unsigned long, std::__1::__map_value_compare<std::__1::basic_string<char>, std::__1::__value_type<std::__1::basic_string<char>, std::__1::basic_string<char> >, std::__1::less<std::__1::basic_string<char> >, true> >=Q}}}}}
{ObjCType=#{unique_ptr<CppType, std::__1::default_delete<CppType> >={__compressed_pair<CppType *, std::__1::default_delete<CppType> >=^{CppType}}}}

The Objective C runtime considers C++ pointers and C++ references to be the same thing as far as encodings are concerned. Foo& and Foo* both end up encoded as ^{Foo={…}}. Unfortunately objc_metadata_hider_ptr doesn’t work with C++ references. Luckily there is objc_metadata_hider_ref that does…

Luckily the compiler will prevent you from doing something dumb like declaring a @property with a std::unique_ptr. If it didn’t, the first time you accessed the value using self.foo your class would lose ownership of the pointer.

@interface Foo {
std::unique_ptr<std::map<std::string, std::string>> bar;
struct CppType {  
std::map<std::string, std::string> myMap;

Note that you don’t need a struct per C++ type in your Objective C class. You could have a single struct that wraps all of your C++ types.

Final Note

You may also want to track https://bugs.llvm.org/show_bug.cgi?id=39888 which is a clang tidy check for watching for large Objective C encodings.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store