1

I am building a static library for controlling an iOS external accessory. As part of this, I want to present a minimal set of public headers to be distributed with the compiled library.

I have a class that needs to be public, but I want to create some framework private methods on it. I created a category for one of the public classes to add the private methods, including an initializer. This works fine for the project tests, but generates -[MyClass initMethodDefinedInCategory:]: unrecognized selector sent to instance error.

Category header:

#import "MyClass.h"

@interface MyClass (LibraryPrivateMethods)
- (id)initMethod:(NSData *)data;
- (int)someOtherMethod:(NSData *)data;
@end

Using class

#import "MyOtherClass.h"
#import "MyClass.h"
#import "MyClass+LibraryPrivateMethods.h"

@implementation MyOtherClass

#pragma mark - Instance Methods
- (id)initWithData:(NSData *)data
{
    self = [super init];
    if(self)
    {

        MyClass *mc = [[MyClass alloc] initMethod:data]; // errors here
        _property = mc;
    }
    return self;
}

I have tried adding the -ObjC linker flag as suggested in this Apple tech note. I also tried adding the -all_load flag as suggested in this SO answer.

EDIT: Realised I was only adding in XCode build, forgot to check build script, given below.

set -ex

INSTALL_PATH=$WORKSPACE/artifacts
[ -z $INSTALL_PATH ] || INSTALL_PATH=$PWD/artifacts

rm -rf $INSTALL_PATH
mkdir -p $INSTALL_PATH

PROJ=$SRCROOT/MyLib.xcodeproj

xcodebuild -project $PROJ -sdk iphoneos INSTALL_ROOT=$INSTALL_PATH/device install
xcodebuild -project $PROJ -sdk iphonesimulator INSTALL_ROOT=$INSTALL_PATH/simulator install

lipo -create -output $INSTALL_PATH/libMyLib.a $INSTALL_PATH/device/libMyLib.a $INSTALL_PATH/simulator/libMyLib.a
mv $INSTALL_PATH/device/Headers $INSTALL_PATH
rm -rf $INSTALL_PATH/device $INSTALL_PATH/simulator

# create zip for distribution.
(cd $INSTALL_PATH; zip -r ../MyLib-release.zip libMyLib.a Headers ../documentation/LibraryDocs/html ../documentation/LibraryDocs/docset)

Where abouts would I add the -ObjC flag to the build script?

Is there any reason that you cannot have an initializer method in a category? It seems possible as this Apple doc says

Category methods can do anything that methods defined in the class proper can do. At runtime, there’s no difference. The methods the category adds to the class are inherited by all the class’s subclasses, just like other methods.

I then tried using a class extension, but i am not sure how to make the method visible to the framework classes only.

@interface MyClass ()
- (id)initMethod;
@end

If I then call [[MyClass alloc] initMethod] from another class in the library, I get a 'No visible @interface for MyClass declares the selector initMethod' error.

How can I make a method on an object only accessible to other classes in the framework and is not exported in MyClass.h ?

Community
  • 1
  • 1
aj.esler
  • 921
  • 1
  • 8
  • 18
  • That's strange -- I've ran into this exact problem before, and adding the `-ObjC` linker flag solved my problem. Did you try passing the `-ObjC` flag to both of the linker command lines when creating the static library and when linking the final executable? – Adam Rosenfield Nov 06 '12 at 20:38
  • Where is the implementation of your `initMethodDefinedInCategory:` method? Is it in the same .m as the public methods, or is it in a separate .m file? – Darren Nov 06 '12 at 20:54
  • @Darren The implementation is in the MyClass+LibraryPrivateMethods.m file. So a separate file to the public methods. – aj.esler Nov 06 '12 at 20:56
  • @AdamRosenfield You might be onto something. I forgot about the build script. Added it to question. Where would I pass in the -ObjC flag in the build script? Or does XCode automatically do this sort of thing? – aj.esler Nov 06 '12 at 21:03
  • @aj.esler: If you're using `xcodebuild` to build the static library, then you wouldn't pass anything on the command line -- you would add the `-ObjC` flag somewhere in the project settings. If there's no explicit setting for it, there should be an "Additional command line options" field (or something like that) somewhere. – Adam Rosenfield Nov 06 '12 at 21:17
  • @AdamRosenfield I have both the -ObjC and -all_load flags set in the Other Linker Flags section in the targets Build Settings. – aj.esler Nov 06 '12 at 21:34

3 Answers3

1

Put the category declaration in a separate .h file. import this private .h file in whatever .m files in the framework need access to the category methods. Don't package this .h file in the set of public .h files that get distributed for client use.

BTW = don't use a class extension, use a named category.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
0

See the note at the bottom of the Apple Tech note:

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.

Since MyClass+LibraryPrivateMethods.m only contains category code, it sounds like you're seeing the bug described in the note.

Move your private implementation into the main MyClass.m or try the -all_load or -force_load flags.

Darren
  • 25,520
  • 5
  • 61
  • 71
  • My interpretation of this was that I am fine because the library has both classes and categories. There are about a dozen classes all up, and one category. – aj.esler Nov 06 '12 at 21:04
  • 1
    The note pertains to individual object files. MyClass+LibraryPrivateMethods.m will compile to a single object file that does not contain any classes. The bug prevents the linker from dealing with that object file correctly. – Darren Nov 06 '12 at 21:08
0

Could not get this to work within time allocated for the problem. I ended up removing the category and moving the initializer code into the MyOtherClass initWithData method. This compiles fine and works.

aj.esler
  • 921
  • 1
  • 8
  • 18