5

I am defining a custom protocol:

@protocol NGSAuthProvider <NSObject>
- (BOOL)isReady;
- (BOOL)isSessionValid;
- (void)login;
- (void)logout;
- (NSString *)accessToken;
- (BOOL)handleOpenURL:(NSURL *)url;
@end

I want to have different providers. So one is a Facebook provider:

@interface NGSFacebookAuthProvider : NSObject <NGSAuthProvider>
@end

@interface NGSFacebookAuthProvider () <FBSessionDelegate>
@property BOOL ready;
@property(nonatomic, retain) Facebook *facebook;
@property(nonatomic, retain) NSArray *permissions;
@end

@implementation NGSFacebookAuthProvider
//Implementation of fbLogin, fbLogout and the methods in NGSAuthProvider that forward calls to self.facebook
- (NSString *)accessToken
{
  return [self.facebook accessToken];
}

@end

I setup Objection to bind from my class to the protocol.

@interface NGSObjectionModule : ObjectionModule
@end

@implementation NGSObjectionModule

- (void)configure 
{
   self bind:[NGSFacebookAuthProvider class] toProtocol:@protocol(NGSAuthProvider)];
}
@end

I setup the Global Injector:

@implementation NGSAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  ObjectionModule *module = [[NGSObjectionModule alloc] init];
  ObjectionInjector *injector = [Objection createInjector:module];
  [module release];

  [Objection setGlobalInjector:injector];
}

I am using this in my RootViewController like this:

@interface RootViewController : UITableViewController
@end

@interface RootViewController ()
@property(nonatomic, retain) id<NGSAuthProvider> authProvider;
@end

@implementation RootViewController
- (void)viewDidLoad {
  [super viewDidLoad];
  self.authProvider = [[Objection globalInjector] getObject:@protocol(NGSAuthProvider)];
}

- (void)processConfig {
  NSString *token = [self.authProvider accessToken];
  // use the access token
}
@end

When I run this, I get the following error:

2011-07-26 21:46:10.544 ngs[6133:b603] +[NGSFacebookAuthProvider accessToken]: unrecognized selector sent to class 0x30c7c
2011-07-26 21:46:10.546 ngs[6133:b603] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NGSFacebookAuthProvider accessToken]: unrecognized selector sent to class 0x30c7c'
*** Call stack at first throw:
(
    0   CoreFoundation                      0x00e825a9 __exceptionPreprocess + 185
    1   libobjc.A.dylib                     0x00fd6313 objc_exception_throw + 44
    2   CoreFoundation                      0x00e8417b +[NSObject(NSObject) doesNotRecognizeSelector:] + 187
    3   CoreFoundation                      0x00df3966 ___forwarding___ + 966
    4   CoreFoundation                      0x00df3522 _CF_forwarding_prep_0 + 50
    5   ngs                                 0x0000324b -[RootViewController processConfig] + 731
    6   ngs                                 0x000041a2 __33-[RootViewController viewDidLoad]_block_invoke_0 + 50

So my class implements the protocol. It successfully is assigned to id<NGSAuthProvider>. I tried contructing [[NGSFacebookAuthProvider alloc] init] explicitly instead of using Objection and it still crashed.

I tried looping through the selectors using objc/runtime.h methods to see which selectors are there but the only thing it finds is initialize:

- (void)logSelectors:(id)obj
{
    int i=0;
    unsigned int mc = 0;
    Method * mlist = class_copyMethodList(object_getClass([obj class]), &mc);
    NSLog(@"%d methods", mc);
    for(i=0;i<mc;i++)
        NSLog(@"Method no #%d: %s", i, sel_getName(method_getName(mlist[i])));

    free(mlist);
}

This has to be something simple that I am missing. I use protocols defined by Cocoa and don't have this issue. I have defined custom protocols for UIViewController-based delegates without issue.

I am stumped as to why Obj-C runtime can't find my methods! If I change id<NGSAuthProvider> to NGSFacebookAuthProvider and construct it explicitly then it all works.

SOLUTION:

The problem was I misunderstood how to bind to a protocol. One way that works is:

@implementation NGSObjectionModule

- (void)configure 
{
  [self bind:[[[NGSFacebookAuthProvider alloc] init] autorelease] toProtocol:@protocol(NGSAuthProvider)];
}
@end

What I would like to do is bind a class to a protocol, but Objection probably wouldn't know the initializer to call?

chrish
  • 2,352
  • 1
  • 17
  • 32
  • Hi,I referred [http://ravelantunes.com/blog/mature-dependency-injection/]. But [ - (void)configure { [self bind:[MyUserService class] toProtocol:@protocol(UserService)]; } ] has an Error in Defining class . Your Solution worked !!!! – Vinod Supnekar Jul 01 '16 at 07:51

3 Answers3

0

I also faced the same issue as I was using

[self bind:[MyClass class] toProtocol:@protocol(MyProtocol)];

The right way for it to work is

[self bindClass:[MyClass class] toProtocol:@protocol(MyProtocol)];
Manvik
  • 977
  • 14
  • 26
0

The issue is you're trying to use the static class method (denoted because you've got a +) instead of a method run on an instance of your object (which is what you've written it as, with a -)

Nektarios
  • 10,173
  • 8
  • 63
  • 93
  • I don't see where I am using a class method. – chrish Jul 27 '11 at 02:41
  • Me either. But somehow you are, right? Maybe you think you have an instance of an object but it's a pointer to the type itself (that's how class methods work under the hood I assume) – Nektarios Jul 27 '11 at 02:49
  • 1
    You comment about calling a static class method got me thinking. The problem is how I am configuring Objection. I thought I was setting it up to use a class object to instantiate an instance when requested for a protocol. I have to actually create an instance when configuring it. I will update the question to include the solution. Now I see in the debug output that it couldn't find `+[NGSFacebookAuthProvider accessToken]`. – chrish Jul 27 '11 at 03:26
0

Chris,

You could use Objection's meta class bindings that allows you to bind a meta class to a protocol and invoke class methods against the meta class instance.

For example,

[self bindMetaClass:[NGSFacebookAuthProvider class] toProtocol:@protocol(NGSAuthProvider)];

But only if you want to use class methods. Otherwise you can use the protocol bindings against a share instance.

justice
  • 400
  • 1
  • 5
  • 6
  • I am not quite sure why you would bind a protocol to the class object. So would the class object have to implement the protocol? – chrish Aug 04 '11 at 12:58
  • The protocol reflects the interface (it doesn't have to actually, but you'll get compiler warnings if you don't) of the meta class. The protocol simply acts as a binding mechanism so that Objection knows what object to bind a to property. Since there isn't a way to define a property that reflects the interface of the meta class the protocol is used to do that. See, https://github.com/atomicobject/objection and read the meta class binding section. – justice Aug 04 '11 at 19:13
  • Ok, well that isn't what I wanted (i.e. class methods). I had several different implementations of the `NGSAuthProvider` protocol and I wanted to configure Objection to construct an instance of a specific class when requested for a protocol. So I wanted `[self bind:[NGSFacebookAuthProvider class] toProtocol:@protocol(NGSAuthProvider)]` to cause Objection to create an instance of `NGSFacebookAuthProvider` but it instead provided the class object. – chrish Aug 04 '11 at 20:02
  • Ah, yes. In this case you could use a shared instance `[self bind:[NGSFacebookAuthProvider sharedProvider] toProtocol:@protocol(NSGAuthProvider)]` or use a provider mechanism to build an instance and return it: `[self bindProvider:[[[CarProvider alloc] init] autorelease] toClass:[Car class]]` or `[self bindBlock:^(JSObjectionInjector *context) { // Manually build object return car; } toClass:[Car class]]; }` – justice Aug 04 '11 at 20:09
  • If my class has `objection_register_singleton(Class)` then I don't have to implement the `sharedProvider` class property, right? – chrish Aug 04 '11 at 21:21
  • Yes, if Objection is responsible for creating that object. – justice Aug 04 '11 at 21:25