[Objective-C] Categories, Static Libraries and Gotchas

2011 April 23, 23:44 h

As some of you may know, I have this small pet project called ObjC Rubyfication, a personal exercise in writing some Ruby-like syntax within Objective-C. Most of this project uses the fact that we can reopen Objective-C standard classes – very much like Ruby, unlike Java – and insert our own code – through Categories, similar to Ruby’s modules.

The idea of this pet project is to be a Static Library that I can easily add to any other project and have all of its features. In this article I’d like to present how I am organizing its many subprojects within one project (and I am accepting any suggestions and tips to make it better as I am just learning how to organize things within Obj-C projects) and talk about a gotcha that took me hours to figure out and might help someone else.

To make this exercise even more fun, I also added a separated target for my unit testing suite (and see how XCode supports tests), then another target for the Kiwi BDD testing framework for Obj-C, and another one for CocoaOniguruma as I have just explained in my previous article.

I’ve been playing with ways of reorganizing my project and I realized that I was doing it wrong. I was adding all the source files from my “Rubyfication” target into my Specs target. So everything was compiling fine, the specs were all passing, but the way I defined dependencies was wrong. It is kind of complicated to understand at first, but it should be something like this:

If you keep creating new targets manually, XCode 4 will also create a bunch of Schemes that you don’t really need. I keep mine clean with just the Rubyfication scheme. You can access the “Product” menu and the “Edit Scheme” option. Then my Scheme looks like this:

I usually configure all my build settings to use “LLVM Compiler 2.0” for the Debug settings and “LLVM GCC 4.2” for the Release settings (actually, I do that for precaution as I am not aware if people are actually deploying binaries in production compiled from LLVM).

I also set the “Targeted Device Family” to “iPhone/iPad” and I try to make the “iOS Deployment Target” to “iOS 3.0” whenever possible. People usually leave the default one which will be the latest release – now at 4.3. Be aware that your project may not run on older devices that way.

Finally I also make sure that the “Framework Search Paths” are pointing to these options:

1
2
"$(SDKROOT)/Developer/Library/Frameworks"
"${DEVELOPER_LIBRARY_DIR}/Frameworks"

Everything compiles just fine that way. Then I can press “Command-U” (or go to the “Product” menu, “Test” option) to build the “RubyficationTests” target. It builds all the target dependencies, links everything together and runs the final script to execute the tests (you must make sure that you are selecting the “Rubyfication – iPhone 4.3 Simulator” in the Scheme Menu). It will fire up the Simulator so it can run the specs.

But then I was receiving:

1
2
3
4
5
Test Suite '/Users/akitaonrails/Library/Developer/Xcode/DerivedData/Rubyfication-gfqxbgyxicfpxugauehktilpmwzv/Build/Products/Debug-iphonesimulator/RubyficationTests.octest(Tests)' started at 2011-04-24 02:16:27 +0000
Test Suite 'CollectionSpec' started at 2011-04-24 02:16:27 +0000
Test Case '-[CollectionSpec runSpec]' started.
2011-04-23 23:16:27.506 otest[40298:903] -[__NSArrayI each:]: unrecognized selector sent to instance 0xe51a30
2011-04-23 23:16:27.508 otest[40298:903] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI each:]: unrecognized selector sent to instance 0xe51a30'

It says that an instance of NSArray is not recognizing the selector each: sent to it in the CollectionSpec file. It is probably this snippet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import "Kiwi.h"
#import "NSArray+functional.h"
#import "NSArray+helpers.h"
#import "NSArray+activesupport.h"

SPEC_BEGIN(CollectionSpec)

describe(@"NSArray", ^{
  __block NSArray* list = nil;
      
  context(@"Functional", ^{
      beforeEach(^{
          list = [NSArray arrayWithObjects:@"a", @"b", @"c", nil];
      });
      
      it(@"should iterate sequentially through the entire collection of items", ^{
          NSMutableArray* output = [[NSMutableArray alloc] init];
          [list each:^(id item) {
              [output addObject:item];
          }];
          [[theValue([output count]) should] equal:theValue([list count])];
      });
...

Reference: CollectionSpec.m

Notice that at Line 3 there is the correct import statement where the NSArray(Helpers) category is defined with the correct each: method declared. The error is happening at the spec in line 18 in the above snippet.

Now, this was not a compile time error, it was a runtime error. So the import statement is finding the correct file and compiling but something in the linking phase is not going correctly and at runtime the NSArray(Helpers) category, and probably other categories, are not available.

It took me a few hours of research but I finally figured out one simple flag that changed everything, the -all_load linker flag. As the documentation states:

Important: For 64-bit and iPhone OS applications, there is a linker bug that prevents -ObjC from loading objects files from static libraries that contain only categories and no classes. The workaround is to use the -all_load or -force_load flags.

-all_load forces the linker to load all object files from every archive it sees, even those without Objective-C code. -force_load is available in Xcode 3.2 and later. It allows finer grain control of archive loading. Each -force_load option must be followed by a path to an archive, and every object file in that archive will be loaded.

So every target that depends on external static libraries that loads Categories has to add this -all_load flag in the “Other Linker Flags”, under the “Linking” category on the “Build Settings” of the target, like this:

So both my RubyficationTests and Rubyfication targets had to receive this new flag. And not the Tests all pass flawlessly!

tags: learning beginner apple objective-c english

Comments

comentários deste blog disponibilizados por Disqus