Punching Obj-C in the Face
So I have issues with CoreData. There’s enough ranting on it elsewhere, so I’ll summarize my key complaints with a short list:
- CoreData isn’t threadsafe.
- CoreData silently corrupts data on failures.
- I didn’t write it, so it’s obviously a piece of shit.
Naturally, the first option is to write my own version of CoreData. Thankfully, there’s a whole documented runtime reference manual on how to interact directly with the Obj-C runtime API (which is all in C). In explanation, if you have the code –
[someObj someMethod:fuck who:you];
The Obj-C compiler basically translates this to something along the lines of:
Class *cls = someObj.isa; Method m = class_getInstanceMethod(cls, "someMethod:who:"); if (!m) { /* Call forwarding functions (ala Ruby's method_missing) to forward the * mis-sent message elsewhere; otherwise raise an * "object-does-not-respond-to-selector" exception */ } IMP imp = method_getImplementation(m); /* imp is an X(*)(void*, SEL, ...) */ return imp(someObj, method_getName(m), fuck, you);
This is all wrapped up by objc_msgSend. These details are important because we effectively want to be able to define a data type as something like
@interface Fuck : BASEOBJ { NSString *dicks; NSArray *butts; } @property NSString *dicks; @property NSArray *butts; @end
And then have BASEOBJ automatically do everything for us. We can do this by injecting our own methods into the class definition with class_addMethod. If you recall, properties in Obj-C basically map to specially-named methods. Since we’re going to be dynamically injecting methods to our class instances, we have to please the type restrictions of the compiler by telling it we’re going to do this (it’s what CoreData does) with the @dynamic directive (instead of @synthesize):
@implementation Fuck @dynamic dicks; @dynamic butts; @end
Then, during our initialization code, the properties can be enumerated class_copyPropertyList:
void injectProperties(Class cls) { unsigned int numProps; objc_property_t *propList = class_copyPropertyList(cls, &numProps); for (unsigned int i = 0; i < numProps; ++i) { objc_property_t prop = propList[i]; const char *getPropName = property_getName(prop); class_addMethod ( cls , sel_registerName(getPropName) , (IMP) &getTypedProxy , "@@:" ); const char *setPropName = strcat("set", capitalize(getPropName)); class_addMethod (cls , sel_registerName(setPropName) , (IMP) &setTypedProxy , "v@:@" ); free(setPropName); } free(propList); }
The important bits to note here is that IMP is just a typedef for void*(*)(void*, SEL, ...) so we can cast basically any function to it. sel_registerName is the C-equivalent of NSSelectorFromString. getTypedProxy and setTypedProxy are two functions that implement the IMP interface and handle our getting/setting functions (I have them load/store values from Sqlite3, for example, but you can make them do anything). The last argument of class_addMethod is a convoluted clusterfuck -- it's basically the type signature of the IMP being passed in, encoded using Obj-C's bullshit method for RTT encoding. In this case, our methods are well-typed (get simply returns an Obj-C object; set takes an Obj-C object and returns void).
So, at this point we're reflecting on the defined properties of all the objects we want (we could take it a step further and check if the methods we're installing exist -- we could make it so we object inject for @dynamic properties which don't currently have a method assigned). We can do fucking anything from here. We're at the core of the runtime, and we're elbow-deep in it.
Fuck yeah.
Xcode just crashed.
7 comments