Objective-c class extensions not effectives in static library - objective-c

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.

Related

Calling a Method on Watchkit

I attended to a watchkit hackathon yesterday and I had some problems regarding calling a method on an NSObject class which uses the Google Maps API and send local notifications. If I call this method from my Watchkit extension, the code doesn't compile, but If I call from the ViewController, for example, everything works perfectly
#import "InterfaceController.h"
#import "Methods.h"
#interface InterfaceController()
#end
#implementation InterfaceController
- (instancetype)initWithContext:(id)context {
self = [super initWithContext:context];
if (self){
// Initialize variables here.
// Configure interface objects here.
NSLog(#"%# initWithContext", self);
}
return self;
}
- (IBAction)butRoute
{
Methods *mt = [[Methods alloc]init];
[mt notif:#"ARRIVING!"];
//***** If I call this method, my code won't compile!!! *****
}
- (void)willActivate {
// This method is called when watch view controller is about to be visible to user
NSLog(#"%# will activate", self);
}
- (void)didDeactivate {
// This method is called when watch view controller is no longer visible
NSLog(#"%# did deactivate", self);
}
#end
The error I get is:
Check the target for your Methods class and make sure it is in your watch kit extensions target.
Alternately, look at building a framework for your shared classes. https://developer.apple.com/videos/wwdc/2014/?id=416
I don't know what xcode version you are using but take into account that the initWithContext method is no longer valid. You should be using:
- (void)awakeWithContext:(id)context
And you shouldn't be overwriting it, just use it.
just remove #import line and replace it with WatchKit framework.

Defining a class that uses different classes depending on the platform

I want to be able to have two classes that are responsible to respond to selectors differently depending if the platform is iOS or OSX.
However, I want to have code that uses only one class, and I want to avoid repeating #ifdefs.
Ideally I would want to have 3 classes:
UniversalClass
iOSSpecificClass
OSXSpecificClass
iOSSpecificClass and OSXSpecificClass both extend UniversalClass.
All calls would be done to UniversalClass, and that class is responsible to call the respective methods of iOSSpecificClass and OSXSpecificClass.
There are two solutions that I came up with:
#interface UniversalClass : NSObject
+ (void) universalMethod;
#end
#implementation UniversalClass
+(id)forwardingTargetForSelector:(SEL)aSelector {
#if TARGET_OS_IPHONE
return [iOSSpecificClass class];
#else
return [OSXSpecificClass class];
#endif
}
#end
The problem with this approach is that UniversalClass promises something in the .h that can or cannot deliver. The warnings also tell us that. Grr. Warnings.
The second approach would be like this:
#implementation UniversalClass
+ (Class)correctClass {
Class aClass = Nil;
#if TARGET_OS_IPHONE
aClass = [iOSSpecificClass class];
#else
aClass = [OSXSpecificClass class];
#endif
return aClass;
}
+ (void)universalMethod {
Class masterClass = [UniversalClass correctClass];
[masterClass universalMethod];
}
#end
The problem with this approach is that I have to perform changes for every method I add and I feel that I am kinda repeating myself without needing.
What are the edge cases I have to pay attention to in both solutions? Is there any better solution than those?
One option is to have a common header file and two different implementations for two targets (one for OSX and another for iOS) that both import and implement the header methods.
Something like this:
Another alternative is to examine if you really need two classes. One #interface and two #implementations (potentially in separate files) is a pattern that I've seen.
Something like (this from CodeRunner where I did my test):
#import <Foundation/Foundation.h>
// #define iPHONE 1
#interface MyClass : NSObject
- (NSString*) someString;
- (BOOL) aMethod: (NSString*) inString;
#end
// common implementations here
#interface MyClass (common)
- (NSString*) commonString;
#end
#implementation MyClass (common)
- (NSString*) commonString
{
return #"same";
}
#end
#ifdef iPHONE
// iPhone specific implementations
#implementation MyClass
- (BOOL) aMethod: (NSString*) inString
{
return [inString isEqualToString: #"iPhone Impl"];
}
- (NSString*) someString
{
return #"iPhone Impl";
}
#end
#else
#implementation MyClass
- (BOOL) aMethod: (NSString*) inString
{
return [inString isEqualToString: #"iPhone Impl"];
}
- (NSString*) someString
{
return #"OS X Impl";
}
#end
#endif
// test
int main(int argc, char *argv[]) {
#autoreleasepool {
MyClass * obj = [[MyClass alloc] init];
NSLog(#"is iPhone? %#", [obj aMethod: [obj someString]] ? #"YES" : #"NO");
NSLog( #"string: %#", [obj someString] );
}
}
You could obviously do this more elegantly by having two .m files and putting one implementation in each (iPhone in one, OS X in the other); or three if you are going to have common routines that are shared by both.
Anyway, just an alternative way to get the same / similar effect - single interface to differing functionality.
You could go with something like this:
#implementation UniversalClass
static Class class;
+ (void)load
{
class = [UniversalClass correctClass];
}
+ (Class)correctClass {
Class aClass = Nil;
#if TARGET_OS_IPHONE
aClass = [iOSSpecificClass class];
#else
aClass = [OSXSpecificClass class];
#endif
return aClass;
}
+ (void)universalMethod {
[class universalMethod];
}
This will keep the promise you made on the .h by implementing the corresponding method (no warnings) and get the right class only once.
How about just ignoring the warning for the specific case of your forwardingTargetForSelector: version? It's like saying “hey, I know what I'm doing!” :-)
Add something like these #pragma calls around your #implementation line:
...
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincomplete-implementation"
#implementation UniversalClass
#pragma clang diagnostic pop
...
See this answer here on Stack Overflow.
The solution that you are proposing is the Class Cluster pattern, which is quite common in Cocoa (e.g. it is used in NSArray, NSValue, etc). Class clusters are classes that return a private subclass from their constructor instead of an instance of the class that was requested. Here is how you might implement that in this case:
MyClass.h
#interface MyClass : NSObject
- (void)someMethod;
#end
MyClass.m
#implementation MyClass
+ (id)alloc
{
if (self == [MyClass class])
{
#if TARGET_OS_IPHONE
return [MyClass_iOS alloc];
#else
return [MyClass_Mac alloc];
#endif
}
else
{
return [super alloc];
}
}
- (void)someMethod
{
//abstract, will be overridden
}
#end
MyClass_iOS and MyClass_Mac would be declared in separate files and privately imported in the McClass.m file.
This seems like a pretty elegant solution at first, but it's not really appropriate for this situation. Class clusters are great for swapping class implementation at runtime when you don't know which implementation you want at compile time (good examples would be supporting different iOS versions, or universal apps that behave differently on iPad and iPhone) but for Mac/iOS we know at compile time which code we need, so introducing a cluster of 3 separate classes is redundant.
This solution doesn't really offer any benefit over the ones suggested by https://stackoverflow.com/users/145108/dad or https://stackoverflow.com/users/3365314/miguel-ferreira because we still have to branch the import code:
#if TARGET_OS_IPHONE
#import "MyClass_iOS.h"
#else
#import "MyClass_Mac.h"
#endif
We could solve that by having a single header for both MyClass_iOS and MyClass_Mac (which was Miguel's solution) or by having both implementations in the same file (which was Dad's solution) but then we've just built a layer on top of one of the solutions you already rejected.
Personally, I would just use a single .m file with three clearly delineated sections:
#interface MyClass
#pragma mark -
#pragma mark Common code
- (void)someMethod1
{
}
#pragma mark -
#pragma mark iOS code
#if TARGET_OS_IPHONE
- (void)someMethod2
{
}
#pragma mark -
#pragma mark Mac code
#else
- (void)someMethod2
{
}
#endif
#end
This avoids creating unnecessary classes and gives you freedom to easily have shared methods or separate implementations for each platform without exposing any of that in the interface.
If the classes for the two platforms definitely won't have any code in common, I'd probably opt for Miguel's solution, which is very clean.
I don't accept the "user confusion" explanation. You'd basically have these three files:
MyClass.h
MyClass_iOS.m
MyClass_Mac.m
I think if someone is confused by what that means, they shouldn't be working on your code base ;-)
You could also combine this with the class cluster approach if you did want to inherit shared code between the two platforms, in which case your MyClass.m file would contain both the shared implementation and the private interface:
#interface MyClass_Private : MyClass
- (void)somePlatformSpecificMethod;
#end
#implementation MyClass
+ (id)alloc
{
if (self == [MyClass class])
{
return [MyClass_Private alloc];
}
else
{
return [super alloc];
}
}
- (void)someSharedMethod
{
//concrete implementation
}
#end
And your project structure would look more like this:
MyClass.h
MyClass.m
MyClass_Private_iOS.m
MyClass_Private_Mac.m
Hope that helps!

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

Objection dependency inject framework - binding a class to a protocol

I'm a bit confused trying utilize Objection for dependency injection, to inject a concrete class for a protocol property instance. For learning purposes I was making a simple logger injection example as follows:
// Protocol definition
#protocol TestLogger<NSObject>
-(void)trace: (NSString*) message, ...;
-(void)info: (NSString*) message,...;
-(void)warn: (NSString*) message,...;
-(void)error: (NSString*) message, ...;
#end
// Concrete class definition following my protocol - note it doesn't actually use
// CocoaLumberjack yet, I just had an NSLog statement for testing purposes
#interface CocoaLumberjackLogger : NSObject<TestLogger>
#end
// Implementation section for lumberjack logger
#implementation CocoaLumberjackLogger
-(void)trace: (NSString*) message, ...
{
va_list args;
va_start(args, message);
[self writeMessage:#"Trace" message:message];
va_end(args);
}
//(note: other implementations omitted here, but are in my code)
.
.
.
#end
Now I want to inject my logger into a view as a property so I do the following:
// My test view controller interface section
#interface TestViewController : UIViewController
- (IBAction)testIt:(id)sender;
#property id<TestLogger> logger;
#end
// Implementation section
#implementation TestViewController
objection_register(TestViewController)
objection_requires(#"logger")
#synthesize logger;
.
.
.
Lastly I have the application module setup:
#interface ApplicationModule : JSObjectionModule {
}
#end
#implementation ApplicationModule
- (void)configure {
[self bindClass:[CocoaLumberjackLogger class] toProtocol:#protocol(TestLogger)];
}
#end
#implementation TestAppDelegate
#synthesize window = _window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
JSObjectionModule *module = [[ApplicationModule alloc] init];
JSObjectionInjector *injector = [JSObjection createInjector:module];
[JSObjection setDefaultInjector:injector];
return YES;
}
The outcome
Everything seems to run perfectly fine, only my logger property is nil in my test view when I click my test button to call a logger statement. I was hoping it would have been filled out with an object of concrete class type CococoaLumberJackLogger.
Any ideas as to where I went wrong? Any help is greatly appreciated. Thanks!
Sean,
What is responsible for initializing the TestViewController? The TestViewController's initialization must be delegated to the injector.
For example, if a NIB is responsible for instantiating it then the logger would be nil because the NIB has no understanding of TestViewController's dependencies.

Private method declarations in objective-C

Do I have to DECLARE all private methods in .m class file inside
#interface ClassName()
//Privare Methods
#end
before
#implementation ClassName
// Implementation of Private & Instance methods
#end
for every method I am implementing & using OTHER THEN the instance methods (methods declared in .h file)?
X-Code 4 DOES NOT give me WARNING for ALL private methods but ONLY for few of them. For example, it warns me for methods I am calling inside gesture handler functions but not inside other routines/methods. I am confused as to declare all non-instance methods or just declare the ones I get warned for.
XCode won't warn about methods that appear before the point of invocation:
#implementation
- (void) foo:(float)x;
{
NSLog(#"%f", x); // prints 15.000000
}
- (void) bar;
{
[self foo:15.0]; // no warning
[self baz:15.0]; // warning
}
- (void) baz:(float)x;
{
NSLog(#"%f", x); // prints 0.000000 instead of 15.0
}
#end
It is strongly recommended to declare methods which you are warned about, the code above gives one example why.
No you don't have to declare every method you implement. The declaration is needed when the method you call is defined after the method which is calling it in .m file:
- (void) methodA {
[self methodB]; //here you will get a warning if you don't define methodB it in a private class extention
}
- (void) methodB {
}