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.
Related
I'm attempting to write a test for an objective-c class. The class I'm trying to test is MyClass and it looks like this:
#interface MyClass : NSObject
- (void)dispatchEvent:(IMAAdEvent *)event;
#end
In order to test this dispatchEvent method, I need to pass in an instance of IMAAdEvent. The IMAAdEvent class comes from Google's library GoogleAds-IMA-iOS-SDK.
Unfortunately, I can't call init on this class because the init method is marked as NS_UNAVAILABLE. In XCode I get an error that reflects this:
'init' in unavailable
Ideally, I would like to make my own mock subclass of IMAAdEvent like this. Is there some way I can initialize my subclass without calling the unavailable init method on the superclass?:
#interface MockImaAdEvent : IMAAdEvent
#end
#implementation MockImaAdEvent
- (id)init {
// is there something I can do here so that I return an instances
// of the subclass without calling [super init]?
}
#end
As of Xcode 12.5, Swift is no longer able to use the previous solution. The compiler has started returning errors for init() is unavailable on lines of code where new init functions have been added. The Xcode 12.5 Release Notes indicate the following:
Clang now infers the availability of +new from availability annotations on -init methods. Since +new calls [[Foo alloc] init], +new isn’t available unless +init is available.
Despite this release note, there is still a valid workaround. By writing the mock class in Objective-C and using a bridging-header to bring it into Swift, the mock class can still call super.new to get a new instance of the parent class (and then customize the subclass from there).
Here is an example:
#interface MockInAppMessagingCampaignInfo ()
#property (strong, nonatomic) NSString *campaignNameValue;
#end
#implementation MockInAppMessagingCampaignInfo
+ (id)newWithCampaignName:(NSString *)campaignName {
MockInAppMessagingCampaignInfo *newObject = super.new;
newObject.campaignNameValue = campaignName;
return newObject;
}
- (NSString *)campaignName {
self.campaignNameWasCalled = YES;
return self.campaignNameValue ?: #"";
}
#end
If I use a method that's not called init then this seems to work. It still seems really weird to not call [super init] in this function, but it's working and returning a new instance of the MockImaAdEvent class
#interface MockImaAdEvent : IMAAdEvent {
enum IMAAdEventType type;
}
#property (nonatomic) enum IMAAdEventType type;
#end
#implementation MockImaAdEvent
#synthesize type;
- (id)initWithType:(NSInteger)_type {
type = _type;
return self;
}
#end
// in my test I can initialize like this:
MockImaAdEvent *adEvent = [[MockImaAdEvent alloc] initWithType:kIMAAdEvent_LOADED];
An alternative solution for Xcode 12.5 that doesn't require Objective-C bridging-header is to create a custom static initialiser that uses the objc runtime to invoke new.
I have used that for fakes that subclass objects with unavailable initialisers and works great.
Example:
static func customInit() -> SomeObjectFake {
let instance = SomeObjectFake.perform(NSSelectorFromString("new")).takeRetainedValue() as! SomeObjectFake
...
return instance
}
I've created two classes with methods with same name. In one of them it is private, in another - public.
Then somewhere on code i write this:
-(void) doMagic:(id) object {
[(ClassA*)object doSmth];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
ClassB * objB = [[ClassB alloc] init];
[self doMagic:objB];
}
In console i see this:
2012-04-25 23:41:28.183 testmagic[558:403] classB - doSmth
Here's classes' sources:
//.h
#interface ClassA : NSObject
-(void) doSmth;
#end
//.m
#implementation ClassA
-(void)doSmth {
NSLog(#"classA - doSmth");
}
#end
//.h
#interface ClassB : NSObject
#end
//.m
#interface ClassB ()
-(void) doSmth;
#end;
#implementation ClassB
- (void)doSmth {
NSLog(#"classB - doSmth");
}
#end
I know, it's because of "message" nature of methods in Obj-C, and at runtime class possibly do not know which of it's methods are private or public, but here's the question:
How can i make really private method? I heard that with decompiling it's possible to see methods names, so someone can just use my private API. How can i prevent it?
The runtime cannot call what it never knows about. The approach I typically take is to use a static function:
MONObject.h
#interface MONObject : NSObject
// ...
#end
MONObject.m
// 'private' methods and ivars are also visible here
#interface MONObject()
// ...
#end
// typically here:
static void fn(MONObject * const self) {
NSLog(#"%#", [self description]);
}
#implementation MONObject
// ...
// sometimes here:
static void fn2(MONObject * const self) {
NSLog(#"%#", [self description]);
}
#end
A workaround to your problem could be to use a proxy/façade class which internally aggregates an instance of your private class. E.g.:
// .h
#interface FoobarFacade: NSObject
- (void)publicMethod;
#end
// .m
#interface FoobarFacade ()
{
Foobar* impl;
}
#end
#interface Foobar: NSObject
- (void)secretMethod;
#end
#implementation Foobar
- (void)secretMethod { NSLog(#"foobar secret method"); }
#end
#implementation FoobarFacade
- (void)publicMethod {
NSLog(#"façade public method");
[impl secretMethod]; // calling into the secret method
}
#end
Of course this isn't 100% safe either, the runtime puts no barriers as others already told.
Right now you can't have truly private methods. When you are declaring a method in a class extension in the .m file you are just hiding it from being exposed in the public header fle. What you are doing now is considered good design because you are hiding the method from the header file which means people would have to go to some length to find those hidden methods, but they can still find them.
Basically the rule I follow is to put as little as I can into the public header as possible and to put everything else into a class extension. This is all you can really do for now.
If you declare the method in the .h file is public. If you want private visibility you have to declare the method in your .m for example:
#interface ClassB (Private_Methods)
- (void)doSmth;
#end
#implementation ClassB
//Rest of .m
I have an Objective-C class that has a method that is meant to be overridden, which is uses in a different method. Something like this:
#interface BaseClass
- (id)overrideMe;
- (void)doAwesomeThings;
#end
#implementation BaseClass
- (id)overrideMe {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (void)doAwesomeThings {
id stuff = [self overrideMe];
/* do stuff */
}
#end
#interface SubClass : BaseClass
#end
#implementation SubClass
- (id)overrideMe {
/* Actually do things */
return <something>;
}
#end
However, when I create a SubClass and try to use it, it still calls overrideMe on the BaseClass and crashes due to doesNotRecognizeSelector:. (I'm not doing a [super overrideMe] or anything stupid like that).
Is there a way to get BaseClass to call the overridden overrideMe?
What you are describing here should work so your problem is likely elsewhere but we don't have enough information to help diagnose it.
From your description, I'd say either the instance you're messaging is not the class you think it is or you made some typo in your code when declaring the method names.
Run your application under gdb, add a symbolic breakpoint on objc_exception_throw, reproduce your problem. Once your process has stopped on the "doesNotRecognizeSelector" exception, print object description and it's class.
Or log it before calling -overrideMe:
NSLog(#"object: %# class: %#", obj, [obj class])
Write a category for BaseClass to override the method.
#interface BaseClass (MyCategory)
- (id) overrideMe;
#end
#implementation BaseClass (MyCategory)
- (id) overrideMe
{
/* Actually do things */
return <something>;
}
#end
Now all instances of BaseClass will respond to selector overrideMe with the new implementation.
Here is my code:
//ECHOAppDelegate.m
#implementation ECHOAppDelegate
...
#end
//PtyView.m
#interface PtyView (PtyPrivate)
-(void)startTask;
-(void) didRead: (NSNotification *)fileNoty;
#end
#implementation PtyView
...
-(void)startTask {
//starts task
}
#end
Now, how do I trigger "startTask" from ECHOAppDelegate.m? I need to create an instance? I'm a total beginner :D
Any example code would be awesome!
Thanks,
Elijah
-(void)startTask; appears to be private implementation and in theory should not be called from external classes.
To answer your question, you can call it something like this:
PtyView *v = [[PtyView alloc] init];
[v startTask];
[v release];
Though you will get a warning saying, PtyView might not respond to startTask. Since it is not in public interface of class.
Update: Above code assumes that when startTask returns, you are done with this object. But something tells me that you might be using async callbacks. If that is the case then startTask might return immediately and you won't release it then and there. Normally in this case, you will be notified by PtyView about the completion of task. So you release it when the task is complete.
Update2:
Making a method public is easy. You just declare it in the public interface (the header file of class):
//in PtyView.h
#interface PtyView
-(void)startTask;
#end
//in PtyView.m
#implementation PtyView
...
-(void)startTask {
//starts task
}
#end
Notice that there is no category defined in the interface declaration.
self represent the current object.
You just need to call the method like that.
[self startTask];
How about subclassing ECHOAppDelegate? (Then make sure PtyView inherits from NSObject?)
// cf. http://amath.colorado.edu/pub/mac/programs/PseudoTTY.zip
#interface ECHOAppDelegate : PtyView
...
#end
#implementation ECHOAppDelegate
- (id) init
{
self = [super init];
if (!self) return nil;
...
return self;
}
...
[self startTask];
...
#end
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.