Objective-C Method Definition Internals
In a previous article we looked at class definitions and Objective C-class definition structures. Now let's look at how to use them together.
Join the DZone community and get the full member experience.
Join For FreeWe've talked in a previous article about class definitions and we've looked at Objective C-class definition structures. They use an __objc_class
structure which contains information with respect to the class's super class, something called a meta class, a cache, a vTable, and a pointer to class data. We were working with a simple Printer
class:
#import <Foundation/Foundation.h>
@interface Printer : NSObject
@property NSString *str_to_print;
- (void) printMsg;
- (void) printString: (NSString*) message;
@end
@implementation Printer
- (void) printMsg {
printf("%s\n", [self.str_to_print UTF8String]);
}
- (void) printString: (NSString*) message {
self.str_to_print = message;
[self printMsg];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Printer *printer = [[Printer alloc] init];
[printer printString: @"Hello World!"];
}
return 0;
}
Now a metaclass in Objective-C allows you to treat a class as an object, essentially. You can create static methods in Objective C, and the meta class is what allows you to do it. Class definitions can have associated methods and data elements, just like a class instance, and need are implememented in the same way as a result.
Coincidentally, it seems that the objc_class
structure in runtime.h
looks like this:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
}
Which is not what we see when we disassemble the binary. Anyway, let's start looking through class definition we do have, we'll return to this discrepancy later.
The last element in the structure extracted from the executable was an __objc_class_Printer_data
type. The other elements in the disassembled class definition ( _OBJC_METACLASS_$_PRINTER
, _OBJC_CLASS_s_NSObject
, and __objc_empty_cache
) we've either covered or they refer to data structures within the Objective-C runtime. So let's take a look at __objc_class_Printer_data
:
__objc_class_Printer_data:
0000000100001198 struct __objc_data {
0x184, // flags
8, // instance start
16, // instance size
0x0,
aX01, // ivar layout
aPrinter, // name
__objc_class_Printer_methods, // base methods
0x0, // base protocols
__objc_class_Printer_ivars, // ivars
0x0, // weak ivar layout
__objc_class_Printer_properties // base properties
}
This looks promising! Here, we seem to have references to methods, the class name, variables, and properties. We have other missing elements too, like the class name. This seems to contain the missing data from the original defined structure, too. We'll take a look at why these seem to differ in the future, right now we're looking at method definitions. Let's take a look at the __objc_class_Printer_methods
structure:
__objc_class_Printer_methods:
00000001000010d8 struct __objc_method_list {
0x18, // flags
5 // method count
}
00000001000010e0 struct __objc_method {
aPrintmsg, // name
aV1608, // signature
-[Printer printMsg] // implementation
}
00000001000010f8 struct __objc_method {
aPrintstring, // name
aV240816, // signature
-[Printer printString:] // implementation
}
0000000100001110 struct __objc_method {
aCxxdestruct, // name
aV1608, // signature
-[Printer .cxx_destruct] // implementation
}
0000000100001128 struct __objc_method {
aStrtoprint_100000f18, // name
a1608, // signature
-[Printer str_to_print] // implementation
}
0000000100001140 struct __objc_method {
aSetstrtoprint, // name
aV240816, // signature
-[Printer setStr_to_print:] // implementation
}
This is what we're looking for — let's take a closer look. This is a list of methods, defined by the __objc_method_list
structure. We have a list of five methods here, some of which we defined (e.g. printString
) and some we didn't (e.g. cxx_destruct
). Each of these entries have pointers to the implementation functions too (I'm using Hopper here, which is very friendly to Objective-C disassembly and function demangling, which is why this section of the disassembly is so easy to read).
Each of the method structures define the method name, signature, and a pointer to an implementation function. Let's take a look at the decompiled printString method implementation:
-(void)printString:(void *)arg2 {
var_18 = 0x0;
objc_storeStrong(&var_18, arg2);
[self setStr_to_print:var_18];
[self printMsg];
objc_storeStrong(&var_18, 0x0);
return;
}
And here's our original:
- (void) printString: (NSString*) message {
self.str_to_print = message;
[self printMsg];
}
Pretty close! We have a few extra things in the decompiled method related to reference counting, but the code is pretty much what we'd expect.
So now we see how classes are defined, or at least some of the major elements (we haven't discussed Protocols or Properties, for example, but you can see how properties are supported in the disassembly here, specifically via the setStr_to_print
method).
There's lots of interesting stuff to continue to unravel here — how these methods are actually called, for example, or how Protocols are supported, or why we see the data stored in the executable that seems a bit off from header file definitions. We'll continue to explore some of this later, but it's time we took a look at how this kind of thing is implemented in Swift.
Opinions expressed by DZone contributors are their own.
Comments