Can't get MonoTouch binding library to work - objective-c

I can't even get the simplest kind of binding library to work - I must be doing something fundamentally wrong...
I created an Xcode project called “TestLib” that looks like this:
// TestLib.h
#import <Foundation/Foundation.h>
#interface TestLib : NSObject
{
NSString *status;
}
#property (nonatomic,retain) NSString *status;
#end
// TestLib.m
#import "TestLib.h"
#implementation TestLib
#synthesize status;
- (id)init
{
self = [super init];
if (self) {
[self setStatus:#"initialized"];
}
return self;
}
#end
The project is here: http://dl.dropbox.com/u/31924320/TestLib.tar.gz
I compiled this into “libTestLib.a”.
Then I created the simplest binding library I could (along with a “single view application” to test it), which simply initializes the TestLib class and calls the getter for the status property. I added the library (libTestLib.a) into the binding library so it has the LinkWith file / assembly directive. The ApiDefinition.cs file looks like this:
namespace TestLibBinder
{
[BaseType(typeof(NSObject))]
interface TestLib
{
[Export("status")]
string Status { get; set; }
}
}
That project is here: http://dl.dropbox.com/u/31924320/BindingTest.tar.gz
Unfortunately I get a null reference back from the library when I try to get the Status property…
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
TestLib testLib = new TestLib(); // succeeds!
string status = testLib.Status; // got a null reference
if (status != null)
label.Text = status;
else
label.Text = "binding failed";
}

I got this to work by stepping away from the Binding Library project approach and instead basing my code on the BindingSample in the xamarin sample suite.
Anyone who wants to build a C# binding to objective-C code using MonoTouch should really start by reviewing the Makefile in that project. The approach it outlines is:
Explicitly compile the Objective-C code for the three architectures (ARM6, ARM7, i386)
Use lipo to create a single universal static library
Use btouch to create the DLL and include the static library through the assembly directive (LinkWith) - see AssemblyInfo.cs in that
project - as well as in the btouch commandline through the --link-with
option.
I personally found the process of creating the ApiDefinition.cs wrapper to be fairly straightforward, but there are some common mistakes - e.g. forgetting the trailing colon on a selector that takes one parameter.

Related

How to build an objective-C native plugin in unity for appleTV and MacOSX

I'm trying to write a native plugin to have access to some of the GameKit functionnalities not yet exposed by the Social.localUser unity API (namely the fact that the local user is multiplayer restricted).
I've written a GameKitWrapper.mm file where I declare a function, and its Csharp counterpart. Everything works fine on IOS (the plugin is found and the methods return the right values), but on appleTV and macOSX the .mm file can't be found.
As I understand it, on macOSX and appleTv I should dynamically link the plugin instead of using the "__Internal" keyword which stands for static-linked librairies. But even when I change the [DllImport("__Internal")] to the name of my plugin (I tried DllImport["GameKitNativeWrapper"] and DllImport["GameKitNativeWrapper.mm"]) when building for appleTV or macOSX, it doesn't work and I got this error:
DllNotFoundException: Unable to load DLL 'GameKitNativeWrapper': The specified module could not be found.
As I have read on the documentation it should work. Is there anything missing on my setup to make the plugin work on appleTV and macOSX?
Should I compile the .mm file into a bundle? so that it can be dynamically linked when running the app on AppleTV and/or Mac?
Below is the relative C# source file GameKitHelper.cs
namespace Plugins.GameKit
{
using System.Runtime.InteropServices;
public static class GameKitHelper
{
[DllImport("__Internal")]
private static extern bool IOSIsMultiplayerGamingRestricted();
public static bool IsMultiplayerRestricted()
{
if (DeviceHelper.IsAppleDevice())
{
return IOSIsMultiplayerGamingRestricted();
}
else
{
return false;
}
}
}
}
and this is the releveant GameKitNativeWrapper.mm file:
#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>
#include <Availability.h>
#include <TargetConditionals.h>
#interface GameKitNativeWrapper: NSObject
{
}
#end
#implementation GameKitNativeWrapper
static GameKitNativeWrapper *_sharedInstance;
+(GameKitNativeWrapper*) sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(#"Creating GameKitNativeWrapper shared instance.");
_sharedInstance = [[GameKitNativeWrapper alloc] init];
});
return _sharedInstance;
}
-(id)init
{
self = [super init];
if (self)
[self initHelper];
return self;
}
-(void)initHelper
{
NSLog(#"initHelper called");
}
-(bool)isMultiplayerGamingRestricted
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
return localPlayer.isMultiplayerGamingRestricted;
}
#end
extern "C"
{
bool IOSIsMultiplayerGamingRestricted()
{
return [[GameKitNativeWrapper sharedInstance] isMultiplayerGamingRestricted];
}
}
If you want to use your Objective-C (.mm) to implement the Mac OS plug-in for unity, you should deploy it as a bundle.
To create the bundle project with XCode:
Open XCode.
Select File > New > Project > macOS > Framework & Library > Bundle.

How do I use a C++ static library in Obj-C?

I have a c++ static libray: .a file and staticLibrary.h file.
In the .h file, there is a class I want to access:
typedef enum
{
eStaticLibOperationUnknown = 0
eStaticLibOperationSystemCheck = 1
} enumStaticLibOperation;
typedef enum
{
eStaticLibResultUnknown = 0,
eStaticLibResultNullParameter = 4,
eStaticLibResultWrongParameter = 5
} enumStaticLibResult;
typedef std::function<void(void)> typeCallBack;
class classResultHelper
{
blah blah
};
class staticLibrary
{
public:
staticLibrary(typeCallBack, const char*);
void requestOperation(const char*, size_t);
void requestOperation(enumStaticLibOperation, const char*, size_t);
enumStaticLibResult getResult(char**, size_t*);
};
I used #import "staticLibrary.h" at the top of my viewController.m file. This raised an error as it recognized the C++ to be foreign. I then changed the viewController to a .mm extension, making the file Objective-C++ and removing the error.
But when I try to run staticLibrary* sL = [[staticLibrary alloc] init]; in viewDidLoad, I get an error at the second staticLibrary on the right side. It says "receiver type is not an objective-c class". What am I doing wrong?
When looking at the documentation for using the static library it says:
1.1. new staticLibrary(callback, “en”);
1.2. requestOperation(“enumSystemcheck”, NULL, 0);
1.3. callback();
1.4. getResult(... , ...);"
I believe this is Java (?), and the first line is to make an instance of staticLibrary with those parameters. How would I do that in objective-C?
The code you have in the example isn't Java, it's C++. ObjC++ means that you can mix statements made in C++ or ObjC on a line, it doesn't mean you can use C++ objects as if they were ObjC objects, or with the ObjC syntax.
What people usually do is only include C++ headers and write C++ code in the .mm file itself, and not put any in the header. Write an ObjC class that wraps the part of your C++ library that you want. So in your example, that'd be something like:
JCRStaticLibrary.h
#interface JCRStaticLibrary : NSObject
-(instancetype) initWithCallback: (void(^)(void))inObjCCallback;
-(void) requestOperation: (NSString*)what withBuffer: (void*)buf size: (int)theSize;
#end
JCRStaticLibrary.mm
#import "JCRStaticLibrary.h"
#include "staticLibrary.h"
#interface JCRStaticLibrary ()
{
staticLibrary *_cppStaticLibrary;
}
#end
#implementation JCRStaticLibrary
-(instancetype) initWithCallback: (void(^)(void))inObjCCallback
{
self = [super init];
if( self )
{
_staticLibrary = new staticLibrary( inObjCCallback, "en"); // ObjC++ knows how to turn an ObjC block into a C++ lambda.
// Under the hood it generates code like:
// _staticLibrary = new staticLibrary( [inObjCCallback](){ inObjCCallback(); }, "en");
// Where the [inObjCCallback](){} part is how C++ does lambdas, its equivalent to blocks.
// I.e. it's roughly analogous to ObjC's ^(){}.
}
}
-(void) dealloc
{
delete _staticLibrary;
}
-(void) requestOperation: (NSString*)what withBuffer: (void*)buf size: (int)theSize
{
_staticLibrary->requestOperation( [what UTF8String], buf, theSize );
}
#end
Something like that. I don't know what the parameters to requestOperation in your case actually are, so I just made an educated guess.
Instead of passing in the ObjC block to -initWithCallback:, you could also write your own lambda and make it a method, i.e.:
-(instancetype) init
{
__weak JCRStaticLibrary *weakSelf = self; // Avoid retain circle.
_staticLibrary = new staticLibrary( [weakSelf](){ [weakSelf doCallbackThing]; }, "en"); // ObjC++ knows how to turn an ObjC block into a C++ lambda.
}
-(void) doCallbackThing
{
// Do whatever the callback should do here.
}
It really depends on whether the callback changes every time you create an object of this type (e.g. if a JCRStaticLibrary object represents a command sent over the network) or comes from a small set of commands used over and over again (e.g. you receive a video frame and apply a filter to it, then hand it off to another object, so you really only ever have one callback). In the former case you wanna keep the block, in the latter, having a subclass for each filter might make more sense (unless you want to switch between filters on-the-fly).

Generate gcda-files with Xcode5, iOS7 simulator and XCTest

Being inspired by the solution to this question I tried using the same approach with XCTest.
I've set 'Generate Test Coverage Files=YES' and 'Instrument Program Flow=YES'.
XCode still doesn't produce any gcda files. Anyone have any ideas of how to solve this?
Code:
#import <XCTest/XCTestLog.h>
#interface VATestObserver : XCTestLog
#end
static id mainSuite = nil;
#implementation VATestObserver
+ (void)initialize {
[[NSUserDefaults standardUserDefaults] setValue:#"VATestObserver"
forKey:XCTestObserverClassKey];
[super initialize];
}
- (void)testSuiteDidStart:(XCTestRun *)testRun {
[super testSuiteDidStart:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
if (mainSuite == nil) {
mainSuite = suite;
}
}
- (void)testSuiteDidStop:(XCTestRun *)testRun {
[super testSuiteDidStop:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
if (mainSuite == suite) {
UIApplication* application = [UIApplication sharedApplication];
[application.delegate applicationWillTerminate:application];
}
}
#end
In AppDelegate.m I have:
extern void __gcov_flush(void);
- (void)applicationWillTerminate:(UIApplication *)application {
__gcov_flush();
}
EDIT: I edited the question to reflect the current status (without the red herrings).
EDIT To make it work I had to add the all the files under test to the test target including VATestObserver.
AppDelegate.m
#ifdef DEBUG
+ (void)initialize {
if([self class] == [AppDelegate class]) {
[[NSUserDefaults standardUserDefaults] setValue:#"VATestObserver"
forKey:#"XCTestObserverClass"];
}
}
#endif
VATestObserver.m
#import <XCTest/XCTestLog.h>
#import <XCTest/XCTestSuiteRun.h>
#import <XCTest/XCTest.h>
// Workaround for XCode 5 bug where __gcov_flush is not called properly when Test Coverage flags are set
#interface VATestObserver : XCTestLog
#end
#ifdef DEBUG
extern void __gcov_flush(void);
#endif
static NSUInteger sTestCounter = 0;
static id mainSuite = nil;
#implementation VATestObserver
+ (void)initialize {
[[NSUserDefaults standardUserDefaults] setValue:#"VATestObserver"
forKey:XCTestObserverClassKey];
[super initialize];
}
- (void)testSuiteDidStart:(XCTestRun *)testRun {
[super testSuiteDidStart:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
sTestCounter++;
if (mainSuite == nil) {
mainSuite = suite;
}
}
- (void)testSuiteDidStop:(XCTestRun *)testRun {
sTestCounter--;
[super testSuiteDidStop:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
if (sTestCounter == 0) {
__gcov_flush();
}
}
Update 1:
After reading a bit more about this, 2 things have now become clear to me (emphasis added):
Tests and the tested application are compiled separately. Tests are actually injected into the running application, so the __gcov_flush() must be called inside the application not inside the tests.
— Xcode5 Code Coverage (from cmd-line for CI builds) - Stack Overflow
and,
Again: Injection is complex. Your take away should be: Don’t add .m files from your app to your test target. You’ll get unexpected behavior.
— Testing View Controllers – #1 – Lighter View Controllers
The code below was changed to reflect these two insights…
Update 2:
Added information on how to make this work for static libraries, as requested by #MdaG in the comments. The main changes for libraries is that:
We can flush directly from the -stopObserving method because there isn't a separate app where to inject the tests.
We must register the observer in the +load method because by the time the +initialize is called (when the class is first accessed from the test suite) it's already too late for XCTest to pick it up.
Solution
The other answers here have helped me immensely in setting up code coverage in my project. While exploring them, I believe I've managed to simplify the code for the fix quite a bit.
Considering either one of:
ExampleApp.xcodeproj created from scratch as an "Empty Application"
ExampleLibrary.xcodeproj created as an independent "Cocoa Touch Static Library"
These were the steps I took to enable Code Coverage generation in Xcode 5:
Create the GcovTestObserver.m file with the following code, inside the ExampleAppTests group:
#import <XCTest/XCTestObserver.h>
#interface GcovTestObserver : XCTestObserver
#end
#implementation GcovTestObserver
- (void)stopObserving
{
[super stopObserving];
UIApplication* application = [UIApplication sharedApplication];
[application.delegate applicationWillTerminate:application];
}
#end
When doing a library, since there is no app to call, the flush can be invoked directly from the observer. In that case, add the file to the ExampleLibraryTests group with this code instead:
#import <XCTest/XCTestObserver.h>
#interface GcovTestObserver : XCTestObserver
#end
#implementation GcovTestObserver
- (void)stopObserving
{
[super stopObserving];
extern void __gcov_flush(void);
__gcov_flush();
}
#end
To register the test observer class, add the following code to the #implementation section of either one of:
ExampleAppDelegate.m file, inside the ExampleApp group
ExampleLibrary.m file, inside the ExampleLibrary group
#ifdef DEBUG
+ (void)load {
[[NSUserDefaults standardUserDefaults] setValue:#"XCTestLog,GcovTestObserver"
forKey:#"XCTestObserverClass"];
}
#endif
Previously, this answer suggested to use the +initialize method (and you can still do that in case of Apps) but it doesn't work for libraries…
In the case of a library, the +initialize will probably be executed only when the tests invoke the library code for the first time, and by then it's already too late to register the observer. Using the +load method, the observer registration in always done in time, regardless of which scenario.
In the case of Apps, add the following code to the #implementation section of the ExampleAppDelegate.m file, inside the ExampleApp group, to flush the coverage files on exiting the app:
- (void)applicationWillTerminate:(UIApplication *)application
{
#ifdef DEBUG
extern void __gcov_flush(void);
__gcov_flush();
#endif
}
Enable Generate Test Coverage Files and Instrument Program Flow by setting them to YES in the project build settings (for both the "Example" and "Example Tests" targets).
To do this in an easy and consistent way, I've added a Debug.xcconfig file associated with the project's "Debug" configuration, with the following declarations:
GCC_GENERATE_TEST_COVERAGE_FILES = YES
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES
Make sure all the project's .m files are also included in the "Compile Sources" build phase of the "Example Tests" target. Don't do this: app code belongs to the app target, test code belongs to the test target!
After running the tests for your project, you'l be able to find the generated coverage files for the Example.xcodeproj in here:
cd ~/Library/Developer/Xcode/DerivedData/
find ./Example-* -name *.gcda
Notes
Step 1
The method declaration inside XCTestObserver.h indicates:
/*! Sent immediately after running tests to inform the observer that it's time
to stop observing test progress. Subclasses can override this method, but
they must invoke super's implementation. */
- (void) stopObserving;
Step 2
2.a)
By creating and registering a separate XCTestObserver subclass, we avoid having to interfere directly with the default XCTestLog class.
The constant key declaration inside XCTestObserver.h suggests just that:
/*! Setting the XCTestObserverClass user default to the name of a subclass of
XCTestObserver indicates that XCTest should use that subclass for reporting
test results rather than the default, XCTestLog. You can specify multiple
subclasses of XCTestObserver by specifying a comma between each one, for
example #"XCTestLog,FooObserver". */
XCT_EXPORT NSString * const XCTestObserverClassKey;
2.b)
Even though it's common practice to use if(self == [ExampleAppDelegate class]) around the code inside +initialize [Note: it's now using +load], I find it easier to omit it in this particular case: no need to adjust to the correct class name when doing copy & paste.
Also, the protection against running the code twice isn't really necessary here: this is not included in the release builds, and even if we subclass ExampleAppDelegate there is no problem in running this code more than one.
2.c)
In the case of libraries, the first hint of the problem came from this code comment in the Google Toolbox for Mac project: GTMCodeCovereageApp.m
+ (void)load {
// Using defines and strings so that we don't have to link in XCTest here.
// Must set defaults here. If we set them in XCTest we are too late
// for the observer registration.
// (...)
And as the NSObject Class Reference indicates:
initialize — Initializes the class before it receives its first message
load — Invoked whenever a class or category is added to the Objective-C runtime
The “EmptyLibrary” project
In case someone tries to replicate this process by creating their own "EmptyLibrary" project, please bear in mind that you need to invoke the library code from the default emtpy tests somehow.
If the main library class is not invoked from the tests, the compiler will try to be smart and it won't add it to the runtime (since it's not being called anywhere), so the +load method doesn't get called.
You can simply invoke some harmless method (as Apple suggests in their Coding Guidelines for Cocoa # Class Initialization). For example:
- (void)testExample
{
[ExampleLibrary self];
}
Because you have to create a new XCTestSuiteRun instance in the testSuiteDidStop method, you are not going to get the proper results on an == check. Instead of depending on instance equality, we used a simple counter and call flush when it hits zero, which it will when the top-level XCTestSuite finishes executing. There are probably more clever ways to do this.
First, we had to set 'Generate Test Coverage Files=YES' and 'Instrument Program Flow=YES' in both the Test and main app targets.
#import <XCTest/XCTestLog.h>
#import <XCTest/XCTestSuiteRun.h>
#import <XCTest/XCTest.h>
// Workaround for XCode 5 bug where __gcov_flush is not called properly when Test Coverage flags are set
#interface GCovrTestObserver : XCTestLog
#end
#ifdef DEBUG
extern void __gcov_flush(void);
#endif
static NSUInteger sTestCounter = 0;
static id mainSuite = nil;
#implementation GCovrTestObserver
- (void)testSuiteDidStart:(XCTestRun *)testRun {
[super testSuiteDidStart:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
sTestCounter++;
if (mainSuite == nil) {
mainSuite = suite;
}
}
- (void)testSuiteDidStop:(XCTestRun *)testRun {
sTestCounter--;
[super testSuiteDidStop:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
if (sTestCounter == 0) {
__gcov_flush();
}
}
#end
There was an additional step required, because the +initialize call was not being made on the observer when included in the Test target.
In the AppDelegate, add the following:
#ifdef DEBUG
+(void) initialize {
if([self class] == [AppDelegate class]) {
[[NSUserDefaults standardUserDefaults] setValue:#"GCovrTestObserver"
forKey:#"XCTestObserverClass"];
}
}
#endif
Here's another solution that avoids having to edit your AppDelegate
UIApplication+Instrumented.m (put this in your main target):
#implementation UIApplication (Instrumented)
#ifdef DEBUG
+ (void)load
{
NSString* key = #"XCTestObserverClass";
NSString* observers = [[NSUserDefaults standardUserDefaults] stringForKey:key];
observers = [NSString stringWithFormat:#"%#,%#", observers, #"XCTCoverageFlusher"];
[[NSUserDefaults standardUserDefaults] setValue:observers forKey:key];
}
- (void)xtc_gcov_flush
{
extern void __gcov_flush(void);
__gcov_flush();
}
#endif
#end
XCTCoverageFlusher.m (put this in your test target):
#interface XCTCoverageFlusher : XCTestObserver
#end
#implementation XCTCoverageFlusher
- (void) stopObserving
{
[super stopObserving];
UIApplication* application = [UIApplication sharedApplication];
SEL coverageFlusher = #selector(xtc_gcov_flush);
if ([application respondsToSelector:coverageFlusher])
{
objc_msgSend(application, coverageFlusher);
}
[application.delegate applicationWillTerminate:application];
}
#end
- (void)applicationWillTerminate:(UIApplication*)application must be defined in your application delegate, not in the observer class.
I didn't have any library problems. "-lgov" is not needed and you don't have to add any libraries. Coverage is supported directly by LLVM compiler.
The process for this is a little different if you're using Specta, since it does its own swizzling. The following is working for me:
Test Bundle:
#interface MyReporter : SPTNestedReporter // keeps the default reporter style
#end
#implementation MyReporter
- (void) stopObserving
{
[super stopObserving];
UIApplication* application = [UIApplication sharedApplication];
[application.delegate applicationWillTerminate:application];
}
#end
AppDelegate:
- (void)applicationWillTerminate:(UIApplication *)application
{
#ifdef DEBUG
extern void __gcov_flush(void);
__gcov_flush();
#endif
}
You then need to enable your custom reporter subclass by setting the environmental variable SPECTA_REPORTER_CLASS to MyReporter in the Run section of your main scheme.
GCOV Flush in -(void)applicationWillTerminate didn't work for me, I think because my App is running in Background.
I also set 'Generate Test Coverage Files=YES' and 'Instrument Program Flow=YES' but no gcda-Files.
Then I executed "__gcov_flush()" in -(void)tearDown from the TestClass, which gave me gcda-Files for my TestClass ;)
Then I created the following Function in my AppDelegate:
#interface AppDelegate : UIResponder <UIApplicationDelegate>
+(void)gcovFlush;
#end
#implementation AppDelegate
+(void)gcovFlush{
extern void __gcov_flush(void);
__gcov_flush();
NSLog(#"%s - GCOV FLUSH!", __PRETTY_FUNCTION__);
}
#end
I called [AppDelegate gcovFlush] in my -(void)tearDown and voilá, there are my gcda Files ;)
I hope this helps, bye Chris

acces c++ datamember in objective-c class

What I'm trying to do is quite simple but I probably have the syntax wrong.
I have an Objective-C Interface with a Note class parameter. Note.h is a C++ class that basically looks like this:
#include <string>
using namespace std;
class Note {
public:
string name;
Note(string name){
this->name = name; // ERROR: Cannot find interface declaration for 'Note'
}
};
This is my controller where is using Note. I changed the file extension to .mm
#class Note;
#interface InstrumentGridViewController : UIViewController {
#public
Note* note;
}
#property (nonatomic, retain) Note* note;
And this is how I'm using it:
#implementation InstrumentGridViewController
#synthesize note;
- (void)buttonPressed:(id)sender {
note = Note("fa"); // ERROR: Cannot convert 'Note' to 'Note*' in assignment
NSLog(#"naam van de noot is %s", note->name); // ERROR: Cannot find interface declaration for 'Note'
}
I'm getting these three errors though (I've added them as comments on the right lines)
Any idea how what I'm doing wrong?
You need to allocate the Note object using new:
- (void)buttonPressed:(id)sender
{
if (note != 0)
delete note;
note = new Note("fa");
NSLog(#"naam van de noot is %s", note->name.c_str());
}
However it doesn't seem right to be doing that in the button press action method...
Also don't forget to delete it in your object's dealloc method:
- (void)dealloc
{
delete note;
[super dealloc];
}
Lastly your #property attribute retain is wrong as it's not an Objective-C object; instead use assign and better still make it readonly.
A better way to initialise most objects is by using a const reference to them rather than a copy:
Note(const string &name)
{
this->name = name;
}
Your Note C++ class is not valid. Change its declaration to be instead:
class Note {
public:
string name;
Note(string aName) {
name = aName;
}
};
Also change your InstrumentGridViewController:
- (void)buttonPressed:(id)sender {
note = new Note("fa");
NSLog(#"naam van de noot is %s", note->name);
}
- (void)dealloc {
delete note;
[super dealloc]; // Use this only if not using ARC
}
The Cannot find interface declaration for 'Note' error was caused because of #class Note in my Obj-C controller .h file. It's strange because I have a working sample project where #class is used and it works fine.
I fixed it using forward declarations the way they are described here and here.
// used typedef struct <classname> <classname>
typedef struct Note Note;
// instead of
#class Note
The above goes in the Obj-C header file. In the .mm file goes the #import "Note.h" statement

Objective-c class extensions not effectives in static library

while creating a library that will be used on several projects, I encountered an error that I was not able to resolve by myself.
The library is composed of several "modules" that each declares its set of classes. The modules declares a header file that references the classes. Each module header is included in the library header, and all of them are copied to the library target.
The "GMData" module defines the ORM layer of the library, it declares a "GMInitializerBase" class, its purpose is to initialize the module. It must be called once in the UIApplicationDelegate.
The "GMModel" module contains the base model for the application (Categories, Articles, ...), It must register itself to "GMData" in order to function properly.
Structure:
<Library Root>
Library.h
<GMData>
GMData.h
GMInitializerBase.{h,m}
<GMModel>
GMModel.h
GMInitializerBase+GMModel.{h,m}
Contents of Library.h
#import "GMData.h"
#import "GMModel.h"
Contents of GMData.h
#import "... ORM related headers ..."
#import "GMInitializerBase.h"
Contents of GMInitializerBase.{h,m}
#import "... ORM Classes ..."
#interface GMInitializerBase : NSObject {
}
+ (void) bootstrap;
+ (GMInitializerBase*) initializer; // autoreleased instance creator
- (void) setup;
- (void) setupStore:(GMManagerFactory*)factory; // Setup database connection
- (void) setupHelpers:(GMHelperFactory*)factory; // Register helpers (abstract)
- (void) setupManagers:(GMManagerFactory*)factory; // Register managers (abstract)
#end
#implementation GMInitializerBase
+ (void) bootstrap {
GMInitializerBase* initializer = [self initializer];
[initializer setup];
}
- (void) setup {
/* Breakpoint 01 */
GMHelperFactory* helperFactory = [GMHelperFactory sharedInstance];
GMManagerFactory* managerFactory = [GMManagerFactory sharedInstance];
[self setupStore:managerFactory];
[self setupHelpers:helperFactory];
[self setupManagers:managerFactory];
}
#end
Contents of GMModel.h
#import "... Base Models files ..."
#import "GMInitializerBase+GMModel.h"
Contents of GMInitializerBase+GMModel.{h,m}
#interface GMInitializerBase (GMModel_Additions)
- (void) setup;
- (void) setupGMModelHelpers:(GMHelperFactory*)factory;
- (void) setupGMModelManagers:(GMManagerFactory*)factory;
#end
#implementation GMInitializerBase (GMModel_Additions)
- (void) setup {
/* Breakpoint 02 */
GMHelperFactory* helperFactory = [GMHelperFactory sharedInstance];
GMManagerFactory* managerFactory = [GMManagerFactory sharedInstance];
// parent implementation
[self setupStore:managerFactory];
// current implementation
[self setupGMModelHelpers:helperFactory];
[self setupGMModelManagers:managerFactory];
// parent implementation
[self setupHelpers:helperFactory];
[self setupManagers:managerFactory];
}
- (void) setupGMModelHelpers:(GMHelperFactory*)factory { /* ... */ }
- (void) setupGMModelManagers:(GMManagerFactory*)factory { /* ... */ }
#end
Contents of ProjectAppDelegate.m (located in another project, it includes the library.a and search the "Headers" directory)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[GMInitializerBase initializer] setup];
}
Stops at the first breakpoint (Breakpoint 01)
It crashed when in the library:
I declare an addition without overloading a method;
I declare an addition to a Cocoa class ([NSString toto]) without overloading;
In works when in the test project:
I declare an addition to a Cocoa class ([NSString toto]) without overloading;
I didn't try to overload a library class but I assume it will work too.
My problem is the following: I can't get the addition workingm and I need it.
Thanks for reading, thanks for answering.
Make sure you have the -all_load and -ObjC flags set in the "Other Linker Flags" in the project settings. Categories in a library won't work without them.
In Objective-C, you shouldn't override the method in a category of a class. Say you have
#implementation MyClass
-(void)foo
{
NSLog(#"%#",#"original!");
}
#end
and later you have
#implementation MyClass (MyCategoryA)
-(void)foo
{
NSLog(#"%#",#"categoryA!");
}
#end
#implementation MyClass (MyCategoryB)
-(void)foo
{
NSLog(#"%#",#"categoryB!");
}
#end
Then the result of
MyClass* myInstance=...;
[myInstance foo];
is not reliable, see the discussion in the official documentation. The documentation says it works if you have only one category, but the documentation says at the same time you shouldn't use that feature. So, don't do this.
The sole exception is +load. If a category defines this method, the runtime calls it for each category you define. So, if you want to perform some initialization task per category, +load is the way. Read the official documentation and this blog post by Mike Ash.