Brandon Evans

(Not) Dynamically Exporting Objective-C Classes To JavaScriptCore

It’s not possible, but it’s interesting to know why!

One of the first things I wanted to try when the Objective-C API was added to JavaScriptCore in iOS 7 was dynamically exporting Objective-C classes to utilize them in JS. Part of the motivation behind this was to avoid the verbose requirement of extending and implementing the empty JSExport protocol that normally exposes certain properties and methods of a native class. Nigel Brooke at Steamclock also suggested that this was a possibility, so I started on my way.

What we want to do is, given a class name we want to expose to JS, traverse the method and property information to build a new protocol (extending JSExport) with the same methods and properties. This will export all of the methods and properties to JS. Then, we tell the runtime that this class implements this protocol and we should be good to go. If you haven’t played with the Objective-C runtime before, it isn’t too difficult to deal with, although you will be working with C and manual memory management. I’ll go through the code necessary to do all of this in small chunks.

First let’s make a new protocol that extends JSExport, assuming that we have a Class class variable that we want to export:

const char *protocolName = class_getName(class);
Protocol *protocol = objc_allocateProtocol(protocolName);
protocol_addProtocol(protocol, objc_getProtocol("JSExport"));

Now lets build up a list of methods from the class that we’ll expose to JS. Note that instance and class methods are done separately. First we get a list of methods and how many there are, and then we enumerate that list and use the information about the method to add it to the protocol:

NSUInteger methodCount, classMethodCount;
Method *methods, *classMethods;
methods = class_copyMethodList(class, &methodCount);
for (NSUInteger methodIndex = 0; methodIndex < methodCount; ++methodIndex) {
    Method method = methods[methodIndex];
    protocol_addMethodDescription(protocol, method_getName(method), method_getTypeEncoding(method), YES, YES);
}

Here we’re doing the same for class methods:

classMethods = class_copyMethodList(object_getClass(class), &classMethodCount);
for (NSUInteger methodIndex = 0; methodIndex < classMethodCount; ++methodIndex) {
    Method method = classMethods[methodIndex];
    protocol_addMethodDescription(protocol, method_getName(method), method_getTypeEncoding(method), YES, NO);
}

Now let’s do it for properties. They’re almost exactly the same except that we also need to fetch and use each property’s attributes (the name and type encoding) to add it to the protocol.

NSUInteger propertyCount;
objc_property_t *properties;
properties = class_copyPropertyList(class, &propertyCount);
for (NSUInteger propertyIndex = 0; propertyIndex < propertyCount; ++propertyIndex) {
    objc_property_t property = properties[propertyIndex];
    NSUInteger attributeCount;
    objc_property_attribute_t *attributes = property_copyAttributeList(property, &attributeCount);
    protocol_addProperty(protocol, property_getName(property), attributes, attributeCount, YES, YES);
    free(attributes);
}

And the most important part, adding the new protocol to the class:

objc_registerProtocol(protocol);
class_addProtocol(class, protocol);

You can find the full implementation I made to do this in a gist in the -exportClass:toContext: method. (I skipped a bit here.) But! This doesn’t work. In fact it used to crash. At this point I was disappointed, but determined to find out why. First we’ll need the JavaScriptCore source.

Diving into the source far enough will bring you to objCCallbackFunctionForMethod, the function that creates a JSObjectRef pointing to the native method of an Objective-C class. All it really does, though, is pass some method-specific information to objCCallbackFunctionForInvocation which handles the real work. But there’s something peculiar about this function call:

objCCallbackFunctionForInvocation(context, invocation, isInstanceMethod ? CallbackInstanceMethod : CallbackClassMethod, isInstanceMethod ? cls : nil, _protocol_getMethodTypeEncoding(protocol, sel, YES, isInstanceMethod))

What’s _protocol_getMethodTypeEncoding? Visiting the definition gives us:

// Forward declare some Objective-C runtime internal methods that are not API.
const char *_protocol_getMethodTypeEncoding(Protocol *, SEL, BOOL isRequiredMethod, BOOL isInstanceMethod);

Interesting! Here JavaScriptCore is relying on a private Objective-C runtime function in order to properly expose Objective-C methods. Let’s jump to the runtime implementation, which is also open source, to see how this might be different from the public protocol_getMethodDescription and why JavaScriptCore needs to use it. I’ll note that iOS 7 isn’t available on Apple Open Source yet, but 10.9 is, so that’s where we’ll go. In objc-runtime-new.mm we find the implementation of _protocol_getMethodTypeEncoding with a comment that reads: “Returns nil if the compiler did not emit any extended @encode data.” Even more interesting! So the compiler will emit extended @encode data that we don’t normally get when we use protocol_getMethodDescription.

In fact, when we use both of these we can see exactly how the returned values are different:

// Given this definition:
@protocol Tester
@required
- (BOOL)testDictionary:(NSDictionary *)dictionary error:(NSError **)error;
@end

struct objc_method_description description = protocol_getMethodDescription(@protocol(Tester), @selector(testDictionary:error:), YES, YES);
NSLog(@”%s”, description.types);
// Outputs c32@0:8@16^@24

const char *descriptionString = _protocol_getMethodTypeEncoding(@protocol(Tester), @selector(testDictionary:error:), YES, YES);
NSLog(@”%s”, descriptionString);
// Outputs c32@0:8@”NSDictionary”16^@24

There’s a full gist you can run here.

So this is why we need to have our JSExport protocols defined at compile time, and if you try to use _protocol_getMethodTypeEncoding on a protocol that was created dynamically you still won’t get the extended type info you need. I’d like to say that knowing this allows us to work around the lack of this extra type information in order to export native classes like we first wanted to. Unfortunately, that extra type information is crucial to how JavaScriptCore marshals values into and out of the virtual machine, and there are obvious downsides to trying to get JavaScriptCore to ignore the missing information (I tried, just in case).

If we look back at the JSC source we can now see how this extra type information is used to marshal values. objCCallbackFunctionForInvocation loops over this extra information in order to properly specify how JS types should be marshalled to Objective-C types as well as how many arguments there are for a given method.

You may have noticed that objCCallbackFunctionForInvocation is also called to create JSObjectRefs for Objective-C blocks. In that case it uses the also-private _Block_signature function declared here to return the type encoding of the block’s signature. Although this function is private, the ABI is public and it’s possible to access the signature directly, something that I first learned from Rob Rix’s Obstruct project.

I hope that, despite this disappointing ending, you’ve learned a bit more about the internals of JavaScriptCore and the Objective-C runtime.

Adding a Headlight Buzzer to a 1986 Vanagon

One of the first things we noticed when we got our Vanagon was that there was nothing electrically smart about it. Locks, doors, seats and lights. All of them dumb. The headlights, especially, were going to be a problem since there is no warning when you leave them on. There is, strangely, a buzzer warning if you leave the keys in and open the driver’s door, but you can’t it lock from the outside without the keys anyways. Engineers…

So here’s what we want: when the driver opens the door and the lights are still on, a buzzer sounds. Ideally we don’t remove the ignition key buzzer functionality, so what we want is really: when the driver opens the door and either the lights are still on or the keys are still in, a buzzer sounds.

There are a few different existing methods to add a buzzer warning when you leave the headlights on, but they seemed poorly designed or wouldn’t work with our particular van. I specifically didn’t want to have to add another buzzer or monkey patch the fuse box. GoWesty has a “kit" (it’s a $7 wire) to tie the headlight switch into the stock buzzer, but it only works with certain switches and ours isn’t one of them. I’m guessing they changed in 1987 or mid-year ‘86 or something.

So there’s already a driver’s door switch and a buzzer used to warn when you leave the keys in. There’s a headlight switch that we can’t use directly, or maybe you could, but I ignored this as it saved me from taking the dash apart. So how do we also sound the buzzer when the lights are on? Turns out that there’s a terminal on the back of the fuse box that is high (+12V) when the license plate light is on at the back of the van, and that corresponds to the headlights being on. By joining the ignition key switch and license plate light signals it would sound the buzzer when we wanted.

With that information, I was finally able to make the circuit I needed. First, I connected a yellow wire to the G9 terminal on the back of the fuse box with a spade connector. Terminal G9 is, if you tilt the top of the fuse panel down towards the floor and all of the wires are coming out of the box towards you, the top right-most spade terminal in a group of ten of them. It’s also labelled in the Bentley guide.

Vanagon Door Buzzer

Then I pulled the socket spade connector that came from the ignition switch out of the buzzer’s relay socket, cut off the connector and exposed new bare wire on that wire (ours was grey with a black stripe). The other bare wire from G9 was crimped into a new spade socket connector along with the ignition switch wire.

Vanagon Door Buzzer

It took some plier work to get this spade socket, a 12 gauge I believe, back into the relay socket. I probably could have hooked up a diode to the ignition key switch connection (and vice versa) just to make sure nothing weird happened when the light connection went high, but it’s not like there are any fragile electronics in there.

That’s pretty much it, other than putting it all back together again, and now you can rest assured that a really terrible sounding buzzer will warn you when the lights are on.

In thinking about this, I realized that most of the code in my app — in almost any app — is just some code. Some view controllers doing their things. Animations. Layout. Action methods. Caches. Web views. If that’s true, then why not make our apps open source? Well, we’re not going to. But I can’t help but wonder what it would be like if developers — even developers of for-pay apps — routinely made their apps open source. (Perhaps with a license that says you can’t just re-skin it and sell it as your own.) People would still buy the apps. But I stop there, at wondering. I’m not advocating anything — just thinking about what would happen. It could be interesting. But it sounds scary.

— 

inessential: Imagining a Scripting Language

Publishing as both a paid app and an open source project is something that I’ve been thinking about for a while and right now I have the freedom to do this with a side project at work. I almost want to do it just for the experiment.

Someone republishing the app as their own is more than annoying, but I know that there’s only so much you can do to prevent this and there will always be rule breakers. So my initial concern doing it with this particular project isn’t so much about lost revenue but more that someone who pays for the app feels misled because they a) didn’t know the source was available to build themselves and would have otherwise gone that route or b) the project is made open source after it’s made available for purchase and it comes off as disingenuous. The project has been mostly used in software development (by both developers and designers) during its own development, so I’m not sure how likely the former case will be. The latter can be mitigated by making it clear that if the source isn’t available at launch then it will be soon.

I have another ongoing concern though: what happens when people contribute to the open source project? They rightly deserve credit for their work, but how does money fit in? Of course we can write the license and contributor agreements in any which way. Even if nobody contributed back to the project I think I would still want the source to be available to demonstrate how certain problems were solved. We aren’t an “open company”, although transparency is one reason to make this source available. Is there a way to frame this that doesn’t feel like potentially profiting off of free work?

I learned a lot tearing this bike down and building it back up again, but the frame has always been a little too big for me. It was dropped off at Good Life today so that someone else can get more use out of it than I do now. Hopefully nobody just yanks that seat though.

I learned a lot tearing this bike down and building it back up again, but the frame has always been a little too big for me. It was dropped off at Good Life today so that someone else can get more use out of it than I do now. Hopefully nobody just yanks that seat though.

On our way back from Victoria we stopped by my parents in Red Deer for a quick visit before heading home. Slug had been running really well (slowly, but well) for us the whole time. Getting it inspected before purchasing it, the folks at Tony’s had thought it was in pretty good shape other than the usual things.

Well, about 10KM south of Red Deer one of the usual things happened and we had a big coolant leak. Not just steam, but it was dripping off of one of the exhaust headers. Up until now the coolant had been topped right up so it was strange to be empty all of a sudden, and the dripping was a cause for concern. Water-cooled Vanagon’s are notorious for problems with the cylinder heads, so I was worried there was a crack somewhere.

I called my dad to come take a look. I’m still hesitant to stick my hands too far into the engine bay, but thankfully he wasn’t. He quickly found that he could get a whole finger (!) into the coolant hose exiting the right cylinder head. How this hose hadn’t split open on any of the slow, hot mountain highways on our trip is beyond me, but it must have been the stop in Red Deer, long enough to cool down, followed by the quick jump up to highway temperatures that did it.

It would be pretty hard to find Vanagon parts on a regular day, but a holiday Monday was out of the question. Thankfully Canadian Tire was able to help us find something close to what we needed, with the right bends that we could cut down to match the head on one side and the firewall terminal on the other (that splits off up to the radiator and the heater core). Off we went, my dad driving us there and Wilma in tow. (Wilma! What a champ on the side of the highway. Monique made sure she was okay the whole time, and watched oncoming traffic. Feet from the QEII isn’t a fun place to be elbows deep into a car.)

After we got the new hose in place, which required figuring out the right and wrong ways to get the airbox out, we topped up the coolant and confirmed there were no new leaks. We also figured out that (note for other Vanagon owners) the temperature warning light just means that the coolant tank is below half full, not that everything is melting.

After that, Slug made it back to Calgary just fine. In addition to replacing the original fuel lines, another usual thing that should be done to any old Vanagon, now we get to look forward to replacing the rest of the coolant lines. We couldn’t have figured this out without my dad there to help us though. Thanks Dad.