6

Objective-C's @encode produces C strings to represent any type, including primitives, and classes, like so:

NSLog(@"%s", @encode(int));       // i
NSLog(@"%s", @encode(float));     // f
NSLog(@"%s", @encode(CGRect));    // {CGRect={CGPoint=ff}{CGSize=ff}}
NSLog(@"%s", @encode(NSString));  // {NSString=#}
NSLog(@"%s", @encode(UIView));    // {UIView=#@@@@fi@@I{?=b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b6b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b3b1b1b1b2b2b1}}

So, I can get a meaningful encoding of a class (one that contains the class name) using @encode(ClassName), but it's also in the same format as the encoding of a generic struct (as in the above example).

Now, my question is, given any (valid of course) type encoding, is it possible to find out whether the encoding is of an Objective-C class, and if so, to get the Class object that corresponds to that encoding?

Of course, I probably could just try to parse the class name out of the type encoding, and get the class from that using NSClassFromString, but that just doesn't sound like the right way to do it, or particularly performance-efficient. Is this really the best way to achieve this?

Greg
  • 9,068
  • 6
  • 49
  • 91
  • I'm wondering how determining on the fly if a literal type name is a class or not could possibly be useful, outside of perhaps a diagnostic macro. – Hot Licks Aug 10 '13 at 11:48
  • @HotLicks it's a long story, involving a lot of metaprogramming and abuse of the runtime, but it does work well, I'm just trying to simplify some of it. – Greg Aug 10 '13 at 11:49
  • 3
    I think I don't understand the question fully, but since according to [the runtime programming guide](https://developer.apple.com/library/mac/DOCUMENTATION/Cocoa/Conceptual/ObjCRuntimeGuide/ObjCRuntimeGuide.pdf) `@encode` marks classes with `#`, isn't it a matter of checking that the string starts in `{` and ends in `=#}`, grabbing what's in between, and passing to `NSClassFromString`? – Sergey Kalinichenko Aug 10 '13 at 11:56
  • 1
    @dasblinkenlight it's not quite that simple, because some classes have more than 1 instance variable (see updated code example), but it's still pretty easily parseable. I just want to see if there's a better way to do this, because doing string parsing to get a class name doesn't sound very safe or right to me. – Greg Aug 10 '13 at 12:01
  • @HotLicks fair point. – Greg Aug 10 '13 at 12:13
  • 1
    That there may more than one instance variables does not matter; it is an additional information you don't need. Using `NSClassFromString` is an often used approach. However, it only works if the class is *registered*, i.e. the ObjC runtime knows about this class. If not, you may use the structural information to construct the data layout, but of course not the methods. – Matthias Aug 10 '13 at 16:19
  • I'm curious where you're getting anonymous encoding strings from. Is this metaprogramming you're doing in a public repo somewhere? – jscs Aug 10 '13 at 18:52
  • @PartiallyFinite Huh? If the class has multiple instance variables, it **still** contains `=#`. So dasblinkenlight's suggestion is correct (with the slight modification that one does not search for `=#}` but for `=#` only, which I think is obvious). –  Aug 10 '13 at 20:43
  • @JoshCaswell it might end up in a public repo eventually, but it isn't at the moment. – Greg Aug 10 '13 at 23:51
  • 1
    @H2CO3 as I wrote in my comment, *"still easily parseable"*. It's not the parsing that was the problem, but whether or not that was the correct approach. Seems like it is. – Greg Aug 10 '13 at 23:53
  • I'll be interested to take a look whenever that happens. I too enjoy tinkering with the runtime library. – jscs Aug 11 '13 at 00:19

2 Answers2

6

Unfortunately, I do not think there is a way to be sure that you are working with a class using the encoding.

dasblinkenlight has a good idea in the comments, although he didn't support other instance variables. The full format for a class is {CLASSNAME=#IVARS}. However, this could potentially catch other structures as well, as I will explain below.

The reason classes are encoded like this are that they are treated just like structures by @encode. The { indicates the start of a structure, and is followed by the structure's name and an equals sign, then the contents, and finally }. The reason all classes have # after the equals sign is that the first element in a class structure is of type Class, and that is how this type is encoded. What this means is that any structure beginning with a Class will match this format. If there are any instance variables listed, they will be encoded after the #. For example, here is a reconstruction of UIView using the encoding you posted.

Structure                         Encoding

struct UIView {                   {UIView=
    Class class1;                  #
    id id1, id2, id3, id4;         @@@@  Object pointers always encode as id.
    float float1;                  f
    int int1;                      i
    id id5, id6;                   @@
    unsigned int uint1;            I
    struct /*anonymous*/ {         {?=
        unsigned bitfield1 : 1;     b1   The type of bitfield is not encoded.
        unsigned bitfield2 : 1;     b1   I just chose unsigned.
        ...
        unsigned bitfield15 : 1;    b1
        unsigned bitfield16 : 6;    b6
        unsigned bitfield17 : 1;    b1
        ...
        unsigned bitfield58 : 1;    b1
        unsigned bitfield59 : 3;    b3
        unsigned bitfield60 : 1;    b1
        unsigned bitfield61 : 1;    b1
        unsigned bitfield62 : 1;    b1
        unsigned bitfield63 : 2;    b2
        unsigned bitfield64 : 2;    b2
        unsigned bitfield65 : 1;    b1
    };                             }
}                                 }

(decoded using the _C_* constants in /usr/include/objc/runtime.h)

If you were to put this structure in your code (filling in the ...s), both @encode(UIView) and @encode(struct UIView) will give you the same result. Interestingly, beginning a structure with a class technically would make it a valid class type, and you could successfully send messages to a pointer to one. Beware, however, that only instance variables defined in the class interface are placed in the encoding, as the locations of others are determined at runtime, so taking advantage of this to create your own objects is not advised.

ughoavgfhw
  • 39,734
  • 6
  • 101
  • 123
1

There is no built-in way to do that, but several people saw the potential in reverse-engineering of parsing @encode output. I don't own this project, but nygard's class-dump might provide you with everything you need to make sense out of a @encode string.

zneak
  • 134,922
  • 42
  • 253
  • 328