Teaser Image

qnoid

Markos Charatzas - London, UK




Recently came across the following warning while running an app on the simulator.

objc[3946]: Class JKSerializer is implemented in both /Users/qnoid/Library/Application Support/iPhone Simulator/5.1/Applications/9CEF83D3-C9DE-4951-95DF-CC1A3DB223F8/TBUndefined.app/TBUndefined and /Users/qnoid/Library/Application Support/iPhone Simulator/5.1/Applications/9CEF83D3-C9DE-4951-95DF-CC1A3DB223F8/TBUndefined.app/TBUndefined. One of the two will be used. Which one is undefined.

The thing to note is how the warning points to the exact same location. Specifically the TBUndefined executable. What this means is that the class definition somehow found its way twice in the executable.

A seemingly random crash would also occur when calling NSDictionary#JSONString. Random as in, every now and then the app would be in a state where it would crash. But once in this state, the crash was consistently reproduced. In both cases, the warning above was depicted.

When the crash occurred, the following error was emitted in the console.

2013-02-11 22:57:19.754 TBUndefined[3946:c07] +[JKSerializer serializeObject:options:encodeOption:error:]: unrecognized selector sent to class 0x5bca4

This is seemingly contradictory to the warning. On one hand the JKSerializer class appears to be defined twice. While on the other, one of its method definition is missing.

First thing to do is find where the JKSerializer class is defined. Turns out, this class is part of JSONKit under the JSONKit.m file along with the expected method definition. (It's worth noting that JSONKit was compiled as a static library, part of a set of dependencies as defined using Cocoapods)

At this point decided to take a look at the executable itself for the definition of JKSerializer. Steve Nygard has created a great tool, class-dump, that generates declarations of classes, protocols and categories in a Mach-O file.

The executable can be found at

/Users/qnoid/Library/Developer/Xcode/DerivedData/TBUndefined-dksnnnn-dsvgfybkturbkhxboi-b/Build/Products/Debug-iphonesimulator/TBUndefined.app/TBUndefined

Running a

class-dump TBUndefined > TBUndefined.dump

and searching for JKSerializer under TBUndefined.dump we do find the two definitions as hinted by the warning. Take a look at the methods.

First Occurrence

  @interface JKSerializer : NSObject
    /* properties omitted for brevity */

      + (id)serializeObject:(id)arg1 options:(unsigned int)arg2 encodeOption:(unsigned int)arg3 block:(id)arg4 delegate:(void)arg5 selector:(id)arg6 error:(SEL)arg7;

    /* instance methods omitted for brevity */
      
    @end

Second Occurrence

  @interface JKSerializer : NSObject
      
    /* properties omitted for brevity */

      + (id)serializeObject:(id)arg1 options:(unsigned int)arg2 encodeOption:(unsigned int)arg3 error:(id *)arg4;

    /* instance methods omitted for brevity */

    @end

As you can tell, the method in question, appears to have a different definition to the one the code is calling. Having 3 extra arguments, block:delegate:selector:.

  + (id)serializeObject:options:encodeOption:block:delegate:selector:error:;

Turns out, this is the JSONKit 1.5 definition.

In the next part, we'll see how that definition ended up there and explain the randomness of the crash.

Kudos to mikeash for contributing so much valuable knowledge to the community for free via his blog.

Kudos to alloy for creating Cocoapods.

Note Have created a sample project that demonstrates the above behaviour as a reference.