Objective-c – Using self in class method

class-designclass-methodobjc-protocolobjective c

I came across this code in ShareKit, and I don't understand what the writer had in mind, using self in a class method. There is a warning: Incompatible pointer types sending 'Class' to parameter type id<FBSessionDelegate>. I want to clean up these warnings, so I can see the ones that may hurt later. What can/should I do that won't break this?

This is is the file SHKFacebook.m and the class name is SHKFacebook

+ (void)logout
{
    FBSession *fbSession; 

    if(!SHKFacebookUseSessionProxy){
        fbSession = [FBSession sessionForApplication:SHKFacebookKey
                                                 secret:SHKFacebookSecret
                                               delegate:self];

    }else {
        fbSession = [FBSession sessionForApplication:SHKFacebookKey
                                        getSessionProxy:SHKFacebookSessionProxyURL
                                               delegate:self];
    }

    [fbSession logout];
}

Best Answer

self may be used in a class method as a polymorphic Class instance.

therefore, the class method new can be implemented like this:

+ (id)new
{
  return [[self alloc] init];
}

and would return the correct type for the Class instance that is messaged:

ex a:

NSArray * a = [NSArray new]; // << a is an NSArray

ex b:

NSMutableArray * a = [NSMutableArray new]; // << a is an NSMutableArray

see note below.

So what you are really faced with is ensuring there are only instance methods in the protocol, and that (Class) self's methods map out to adopt the instance methods in the protocol.

As far as the design... well, let's just say I would not have written it this way. A singleton would have been clearer and more correct, but I don't even like singletons so I would not have taken that route.

The warning is produced because the Class instance (what is passed) does adopt the @protocol specified by the delegate parameter. The Class instance is not an instance of the class. A protocol declaration really applies to instances of the class. For example, if you adopt NSLocking, does the compiler expect you to also implement class methods for every instance method declared in the protocol? Answer: Never. The implementation you're dealing with is IMO one of those cases where it's a misuse of the language, but it happens to work.

To clarify the terminology:

The "Class instance" is self in a class method:

+ (void)foo { self; }

An "instance of the class" is self in an instance method:

- (void)foo { self; }

In practice, -[NSObject conformsToProtocol:] is +[NSObject conformsToProtocol:] and +[NSObject class] just returns self so there are no errors at execution.

I'm still not clear, then, why I am receiving a warning, if the code meets the criteria you described.

The criteria I described applies to the execution, but it deviates from the semantics of the language -- thus, the compiler is absolutely correct on this.

For a resolution to the problem: There's no way to tell the compiler "My Class instance conforms to protocol" because declaration of adoption applies to instances of the class.

You have two primary options:

  1. The clean, correct approach: Use an instance of the class and implement the protocol as defined.

  2. or Typecast the class instance to the protocol:

    id delegate = (id)self; fbSession = [FBSession sessionForApplication:SHKFacebookKey getSessionProxy:SHKFacebookSessionProxyURL delegate:delegate];

If you choose #2, it may help to define the instance methods of the protocol to use class methods, like so:

+ (void)protocolMethod { /* do stuff */ }
- (void)protocolMethod { [self.class protocolMethod]; }

that would also imply you never need instances. It would help because it will add warnings if the protocol will change. These warnings would bubble up to class methods when you follow the convention.

To reduce noise, you may also consider creating some method to reduce the typecast to one location:

+ (id<SomeProtocol>)sharedSomeProtocolDelegate
{
  return (id<SomeProtocol>)self;
}

- (id<SomeProtocol>)sharedSomeProtocolDelegate
{
  return [[self class] sharedSomeProtocolDelegate];
}

then you can just write:

fbSession = [FBSession sessionForApplication:SHKFacebookKey
                             getSessionProxy:SHKFacebookSessionProxyURL
                                    delegate:[self sharedSomeProtocolDelegate]];

(note that the implementations of these types are actually class clusters, and you will see something different in the debugger or if printed)

Related Topic