views:

411

answers:

6

I have a project that has a library subproject that gets imported. I have access to the source code of both the main project and the subproject.

The subproject uses Core Text. Because the subproject must be used on pre and post 3.2 applications, Core Text is weak linked and all of the Core Text related code is wrapped in the logic:

#if defined(__CORETEXT__) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2

The thought of this line of code is that if CoreText is not linked in, the code is skipped. If the iPhone version is less than 3.2; CoreText is not linked in.

The reason for this goal is that the main project(s) (and there are several) do not all use Core Text and if the don't then without the 'defined(CORETEXT)`, they will not compile.

This seems to work fine and everything compiles without an error on projects that use Core Text. However on execution, the code is not found and a runtime error along the lines of "NSString does not respond to XXXX" (part of the code is a category on NSString).

Has anyone run into this? Is my question, partially vague due it being client work, clear?

Ideally I want to set this up so that the subproject does not need to be altered when the main project uses Core Text and when it does not.

Note that __CORETEXT__ is defined in the header of the Core Text framework.

Update on the issue

I have tried the suggestions offered so far and they are ineffectual. Perhaps a little more code will help to outline the issue. I have a category with the following header:

@interface NSString (Internal)

- (NSString *)stringByUnescapingEntities;
- (NSString *)flattenHTML;
- (NSString *)flattenHTMLAndParseParagraphBreaks:(BOOL)parseBreaks;
- (NSString *)stringByEscapingForURL;
- (NSString *)stringByEscapingForJSON;

#if defined(__CORETEXT__) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
- (CFAttributedStringRef)attributedStringFromHTML;
- (CFAttributedStringRef)attributedStringFromHTMLWithBaseAttributes:(NSDictionary*)baseAttributes;
#endif

@end

Positive Test

All of the methods outside of the #if block work perfectly. All of the methods inside of the #if compile perfectly, no warnings.

Negative Test

If I change __CORETEXT__ to something like __THIS_IS_NOT_REAL__ I will get a compile error for the methods inside of the block. Therefore I know, at compile time at least, that the __CORETEXT__ flag is defined and fully functional.

Runtime issue

However, when I go to access one of the methods that are declared inside of the #if block, I get the following error at runtime:

-[NSCFString attributedStringFromHTMLWithBaseAttributes:]: unrecognized selector sent to instance 0x689c000

Instance 0x689c000 being a NSString.

If I remove the __CORETEXT__ logic check then everything works so long as the master project is using Core Text. If the master project is not using Core Text then compile errors about missing linkers shows up.

Hopefully that clarifies the issue.

NOTE: All projects are using the -all_load flag; as it was needed for TouchXML already.

I have created a test project that demonstrates the issue.

Test Project

+1  A: 

You could do [NSObject respondsToSelector:] checks to see if the category method is available or not.

Edit: I think I didn't fully read the question the first time around. Here are a couple more things that I saw.

That #if probably won't work. This is because this is all checked at compile-time rather than run-time. I'm not exactly sure on what gets included in when weak-linking, but as I understand it, the header of the weak-linked framework will get processed at compile-time. That means the symbols for the method prototypes will be available when you build, which is why there are no build errors or warnings.

I'm also 99% sure that the iOS check will not work. That only checks the max version allowed, which is set at compile-time. Unless you're making different builds with different settings for each iOS (which I assume not), it's pretty much always going to be the same value regardless of which device you're running on.

You're definitely going to need runtime checks (e.g. the afore mentioned respondsToSelector). Version checking of the iOS device can only be done at runtime as well.

David Liu
Very good point. The issue I am running into is that it is not available in the main project when it should be. It compiles without warnings or errors (and I did a negative check to confirm) but at runtime it is failing.
Marcus S. Zarra
See the new edits for info. I'm not sure what you mean by negative check though. Could you clarify?
David Liu
A: 

The fact that you call out that at least one thing that ends up missing is a category method for NSString makes me think you're running into a lovely issue that seems to be a possible bug in the linker.

Firstly, see this answer elsewhere and ensure that the project including your library is using the -all_load linker flag.

Second, in some dealings I've had with using the Three20 library for the iPhone it became necessary to also use this weird "hack" that applies if, for example, your NSString category is the only thing in the .m file where it's implemented.

//CategoryBugHack.h
#define FIX_CATEGORY_BUG(name) @interface FIX_CATEGORY_BUG##name @end @implementation FIX_CATEGORY_BUG##name @end

Then the category .h/.m ...

//NSStringCategory.h
@interface NSString (MyAdditions)
// etc, etc
@end

//NSStringCategory.m
#import "CategoryBugHack.h"
FIX_CATEGORY_BUG(NSString_MyAdditions)
@implementation NSString (MyAdditions)
// etc, etc
@end

Basically there seems to be an additional issue related to static libraries where an implementation file that contains ONLY implementations of category methods. The FIX_CATEGORY_BUG macro basically just expands to define an interface and implementation of that interface with no methods, just to force something into the .m file that isn't a category.

imaginaryboy
Updated the question. `-all_load` is already used and other methods outside of the `#if` block are working perfectly without the macro you suggested.
Marcus S. Zarra
A: 

Maybe you should make the conditional into

#if defined(__CORETEXT__) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2)

After all, the rest of the referenced project is linked in, correct? The problem must be the preprocessor's conditional.

Huperniketes
if I remove the `defined(__CORETEXT__)` portion it works fine in post 3.2 but then fails in projects that are not using Core Text.
Marcus S. Zarra
Huperniketes
Confounding markdown. And confounding SO for not indicating the formatting rules for comments.
Huperniketes
A: 

I remember I did something similar to David but just for a single method. I used Objective-C runtime functions to call that method so it will compile in every version. I did something like:

if ([theReceiver respondToSelector:@selector(mySelector:)]){
   objc_msgSend(theReceiver, @selector(mySelector:), param1);
}else{
   // do it other way here.
}

Not the most beautiful code I have written, nor I have tried before but maybe is possible to add your methods and ivars with runtime functions like class_addIvar, class_addMethod, etc.

nacho4d
A: 

If the category is housed in the sub project, you may need to set the target of that project. I have had weird side effects like this when doing sub projects within a project.

Edit: Meaning, the subproject could be set to compile against 3.1.3 and possible never meets your logic?

bstahlhood
Subproject is set to base 4.0 and deploy 3.0
Marcus S. Zarra
Have you tried setting it to deploy 3.2 and see if it works? If it's using the deploy setting, then __CORETEXT__ would not be defined and the methods would not be included.
bstahlhood
A: 

I believe I found a similar situation recently, and as I told you on twitter I think you need to set -ObjC in addition to -all_load, as described here: http://developer.apple.com/mac/library/qa/qa2006/qa1490.html

pcuenca
No change in the error. I am going to build a test case and post it shortly.
Marcus S. Zarra