41

In Objective-C, I can add methods to existing classes with a category, e.g.

@interface NSString (MyCategory)
- (BOOL) startsWith: (NSString*) prefix;
@end

Is it also possible to do this with protocols, i.e. if there was a NSString protocol, something like:

@interface <NSString> (MyCategory)
- (BOOL) startsWith: (NSString*) prefix;
@end

I want to do this since I have several extensions to NSObject (the class), using only public NSObject methods, and I want those extensions also to work with objects implementing the protocol .

To give a further example, what if I want to write a method logDescription that prints an object's description to the log:

- (void) logDescription {
    NSLog(@"%@", [self description]);
}

I can of course add this method to NSObject, but there are other classes that do not inherit from NSObject, where I'd also like to have this method, e.g. NSProxy. Since the method only uses public members of protocol , it would be best to add it to the protocol.

Edit: Java 8 now has this with "virtual extension methods" in interfaces: http://cr.openjdk.java.net/~briangoetz/lambda/Defender%20Methods%20v4.pdf. This is exactly what I would like to do in Objective-C. I did not see this question earning this much attention...

Regards, Jochen

Jochen
  • 7,270
  • 5
  • 24
  • 32

7 Answers7

26

Short answer: No.

Long answer: how would this work? Imagine you could add methods to existing protocols? How would this work? Imagine we wanted to add another method to NSCoding, say -(NSArray *) codingKeys; This method is a required method that returns an array of the keys used to encoding the object.

The problem is that there are existing classes (like, say NSString) that already implement NSCoding, but don't implement our codingKeys method. What should happen? How would the pre-compiled framework know what to do when this required message gets sent to a class that does not implement it?

You could say "we can add the definition of this method via a category" or "we could say that any methods added via these protocol categories are explicitly optional". Yes, you could do this and theoretically get around the problem I've described above. But if you're going to do that, you might as well just make it a category in the first place, and then check to make sure the class respondsToSelector: before invoking the method.

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • I think in theory this could work. Since Objective-C allows adding methods to existing classes with class_addMethod, I could get all defined classes, check if they implement a specific protocol and then add my method to the class (since it only uses the protocol's methods, it does not depend on the class). The question came up because we wanted to define an "extended protocol method" once, and not for every class, and have the compiler know about this, so it doesn't have to throw warnings. So even if we can do this with the runtime, the compiler warnings still are there. – Jochen Jan 20 '10 at 00:58
  • 1
    I think that in reality it can work too. C# 3.0 allows to extend interfaces via static classes. It is different mechanism than categories, because it is only syntatic sugar. In Objective-C category really adds methods to class. – Filip Kunc May 23 '10 at 20:35
  • 4
    Here's why it would be useful to add methods to existing protocols: Let's say you have a category on `NSObject` defining `-foo`, and a category on `UIApplication` defining `-bar`. In `-bar`, you want to call `[self.delegate foo]`. `UIApplicationDelegate` conforms to `NSObject`. How do you convince the compiler that calling `-foo` on `self.delegate` is fine? – hatfinch Jun 09 '10 at 23:31
  • @hatfinch red herring. You wouldn't put the method on `` (the protocol), you'd put it on `NSObject` (the class). However, I think the entire premise is wrong. Generally, if you find you have to access the application instance to achieve proper functionality, then you probably need to rethink your app's architecture. – Dave DeLong Jun 10 '10 at 16:52
  • @hatfinch the `` protocol exists so that you can declare variables as `id someVar;` and not get a warning when you try to `[someVar retain];`. `` would be declared as `@protocol SomeProtocol `, thereby giving any `` the standard `retain`, `release`, `respondsToSelector:`, etc methods. – Dave DeLong Jun 10 '10 at 16:53
  • I just used `UIApplicationDelegate` as an example; the point stands for any delegate. I understand the purpose of ``, I just think it would be useful to be able to extend it (in particular, or any protocol in general). For instance, it would be really useful if `-performSelectorOnMainThread:...` etc. were in ``. – hatfinch Jun 12 '10 at 21:22
  • Here's a place where this feature would be useful: I have a field in a class of type `id`, where `MyProtocol` includes ``. I've used a category to add a method to `NSObject`, and want to call that method on this field, but the compiler produces a warning. I wish my category method could be added to ``. – executor21 Jun 16 '10 at 00:01
  • @Executor21 so instead of declaring it as `id foo`, declare it as `NSObject *foo`. – Dave DeLong Jun 16 '10 at 00:03
  • NSObject only works if it's an NSObject. What if it's e.g. a NSProxy. I added a sample for this. – Jochen Jun 27 '10 at 12:59
  • @Jochen it's possible that there are extreme edge cases where this might be useful, but in the years that I've been doing Cocoa development, I've *never* come across a situation where I needed this and couldn't work around it. – Dave DeLong Jun 27 '10 at 16:17
  • @Jochen: "What if it's e.g. a NSProxy" So what if it's an NSProxy? Are you going to add an implementation for NSProxy too? – user102008 Aug 01 '11 at 22:07
  • @Jochen: The declared object type is simply to assist in looking up the correct argument type. You can tell the compiler an object is whatever type you want; the truth of that assertion isn't tested till runtime. Even if you know it's an `NSProxy`, lie to the compiler really sweetly that it's an `NSObject`, and the compiler will believe you and act accordingly. – Jeremy W. Sherman Aug 26 '11 at 21:13
  • Tell me if this case is stupid. I want to make a category on the protocol `NSFastEnumeration` so I can have a `map` method on dictionaries and arrays. Obviously, I can work around this but it would be really nice. I would know that I could use any methods that the protocol declares. – jasongregori Oct 28 '11 at 18:44
  • I reread your answer and I guess I could just put a category on NSObject and double check the object follows the protocol before running my map method. Not as nice since it will show up for all objects but an okay work around. Thanks! – jasongregori Oct 28 '11 at 18:48
  • 4
    Sorry for being so late to this debate but I, like @jasongregory, wanted to do exactly the same thing. Add a bunch of map methods to NSFastEnumerating. There could be multitudes of good reasons to implement a category on a protocol and it would work fine if the category only depended on methods already defined in the protocol. In fact, it may not be possible with the objective-c language constructs but it sould be possible with the runtime since MacRuby can do something similar with mixins and MacRuby is implemented on top of the objective-c runtime. – aLevelOfIndirection Apr 07 '13 at 10:34
  • Adam Sharp posted a solution that works pretty well check it out [here](http://stackoverflow.com/a/17740636/35690). – Senseful Jul 21 '13 at 06:41
  • 4
    And then there was Swift 2.0 and nobody else asked whether categories of a protocol are a good idea ever again. – Dan Rosenstark Nov 13 '15 at 21:57
  • 1
    This answer tries to dismiss the question by raising a number of implementation unknowns (in the answerer's mind).  In reality, these “unknowns” have been answered & implemented before, and can be done again.  At the time of answer-writing Ruby is a good place to look for a robust message-resolution chain that handles category methods (module methods in Ruby terms), and in the present Swift is the go-to implementation in the Apple-verse as others have noted.  I believe Smalltalk also holds some of the answers to the questions frivolously raised in this answer. – Slipp D. Thompson Jul 11 '16 at 22:05
  • The answer's arguments against this are specious. As Swift, other languages, and [extObjC](https://stackoverflow.com/a/19138382/2547229) demonstrate, there are sound and predictable semantics for the OPs request. The extension would require an implementation using existing methods of the protocol. As a further development, conformers to the protocol could override the default implementation. Vanilla ObjC doesn't do this to the best of my knowledge and I would avoid using a third party library that introduced it for production code. – Benjohn Jul 04 '17 at 08:22
  • "how would this work?" - The same way as in Swift. You write an extension for a protocol and then all classes implementing the protocol also have that extension. Obj-C categories could provide just that but the runtime simply doesn't support it. – Mecki May 09 '23 at 08:42
23

While it's true that you can't define categories for protocols (and wouldn't want to, because you don't know anything about the existing object), you can define categories in such a way that the code only applies to an object of the given type that has the desired protocol (sort of like C++'s partial template specialization).

The main use for something like this is when you wish to define a category that depends on a customized version of a class. (Imagine that I have UIViewController subclasses that conform to the Foo protocol, meaning they have the foo property, my category code may have need of the foo property, but I can't apply it to the Foo protocol, and if I simply apply it to UIViewController, the code won't compile by default, and forcing it to compile means someone doing introspection, or just screwing up, might call your code which depends on the protocol. A hybrid approach could work like this:

@protocol Foo
- (void)fooMethod

@property (retain) NSString *foo;
@end

@implementation UIViewController (FooCategory)

- (void)fooMethod {
    if (![self conformsToProtocol:@protocol(Foo)]) {
        return;
    }

    UIViewController<Foo> *me = (UIViewController<Foo>*) self;
    // For the rest of the method, use "me" instead of "self"
    NSLog(@"My foo property is \"%@\"", me.foo);
}
@end

With the hybrid approach, you can write the code only once (per class that is supposed to implement the protocol) and be sure that it won't affect instances of the class that don't conform to the protocol.

The downside is that property synthesis/definition still has to happen in the individual subclasses.

Douglas Mayle
  • 21,063
  • 9
  • 42
  • 57
  • Most brilliant solution to given problem, without relying on “voodoo frameworks” but solely on the existing language features and patterns. This should be the accepted answer. – valeCocoa Dec 11 '17 at 14:17
20

extObjC has the NEATEST stuff you can do with Protocols / Categories... first off is @concreteprotocol...

  • Defines a "concrete protocol," which can provide default implementations of methods within protocol.
  • An @protocol block should exist in a header file, and a corresponding @concreteprotocol block in an implementation file.
  • Any object that declares itself to conform to this protocol will receive its method implementations, but only if no method by the same name already exists.

MyProtocol.h

@protocol MyProtocol 
@required - (void)someRequiredMethod;
@optional - (void)someOptionalMethod;
@concrete - (BOOL)isConcrete;   

MyProtocol.m

 @concreteprotocol(MyProtocol) - (BOOL)isConcrete { return YES; } ...

so declaring an object MyDumbObject : NSObject <MyProtocol> will automatically return YES to isConcrete.

Also, they have pcategoryinterface(PROTOCOL,CATEGORY) which "defines the interface for a category named CATEGORY on a protocol PROTOCOL". Protocol categories contain methods that are automatically applied to any class that declares itself to conform to PROTOCOL." There is an accompanying macro you also have to use in your implementation file. See the docs.

Last, but NOT least / not directly related to @protocols is synthesizeAssociation(CLASS, PROPERTY), which "synthesizes a property for a class using associated objects. This is primarily useful for adding properties to a class within a category. PROPERTY must have been declared with @property in the interface of the specified class (or a category upon it), and must be of object type."

So many of the tools in this library open (way-up) the things you can do with ObjC... from multiple inheritance... to well, your imagination is the limit.

Community
  • 1
  • 1
Alex Gray
  • 16,007
  • 9
  • 96
  • 118
  • I cannot find `pcategoryinterface`. which version do you use? – Hai Feng Kao Jun 04 '16 at 20:55
  • 2
    @HaiFengKao I forked what was once known as the `experimental` branch. Take a look at my master fork, or look at `EXT`'s history/alternate branches for the specific features you need, which may very well have been cut from the mainline repo. – Alex Gray Jun 05 '16 at 01:21
7

It isn't really meaningful to do so since a protocol can't actually implement the method. A protocol is a way of declaring that you support some methods. Adding a method to this list outside the protocol means that all "conforming" classes accidentally declare the new method even though they don't implement it. If some class implemented the NSObject protocol but did not descend from NSObject, and then you added a method to the protocol, that would break the class's conformance.

You can, however, create a new protocol that includes the old one with a declaration like @protocol SpecialObject <NSObject>.

Chuck
  • 234,037
  • 30
  • 302
  • 389
  • 2
    I think what the OP is trying to do is to provide an (extra) implementation for the method. i.e. adding the methods (with implementation) to all objects which implement the protocol. That's kind of different from extending the protocol itself with new method definitions which, of course, wouldn't work for the reason you mentioned. – chakrit Feb 06 '12 at 10:49
0

I think you may be mixing up terms here and there. Extensions, Categories, Protocols, Interfaces and Classes are all different things in Objective-C. In The Objective-C 2.0 Language Apple describes the differences very well, including the benefits and drawbacks to using categories and extensions.

If you think about it, what is a "Category" or "Extension" in the conceptual sense? It's a way of adding functionality to a Class. In Objective-C, protocols are designed to have no implementation. Therefore, how would you add or extend the implementation of something that doesn't have implementation to begin with?

slf
  • 22,595
  • 11
  • 77
  • 101
  • 1
    categories also serve to declare the method in the class (e.g. informal protocols) so that the compiler doesn't warn that the object doesn't support the method, even if you may or may not actually implement it – user102008 Aug 01 '11 at 22:13
0

if you're already writing a category, why not just add in the protocol definition in the header right after the category definition?

i.e.

@interface NSString (MyCategory)
- (BOOL) startsWith: (NSString*) prefix;
@end

@protocol MyExtendedProtocolName <NSString>
//Method declarations go here
@end

this way any class that imports the category header will also get the protocol definition, and you can add it into your class..

@interface MyClass <OriginalProtocol,MyExtendedProtocolName>

also, be careful when subclassing NSString, it's a cluster and you may not always get the behaviour you're expecting.

pxl
  • 1,297
  • 10
  • 16
0

Adam Sharp posted a solution that worked for me.

It involves 3 steps:

  1. Defining the methods you want to add as @optional on a protocol.
  2. Making the objects you want to extend conform to that protocol.
  3. Copying those methods into those objects at runtime.

Check out the link for the full details.

Community
  • 1
  • 1
Senseful
  • 86,719
  • 67
  • 308
  • 465