问题
In Ruby, there's Modules and you can extend a class by "mixing-in" the module.
module MyModule
def printone
print "one"
end
end
class MyClass
include MyModule
end
theOne = MyClass.new
theOne.printone
>> one
In Objective-C, I find that I have a set of common methods that I want a number of Class to "inherit". What other ways can I achieve this without creating a common class and deriving all from that common class?
回答1:
Edit: changes added because some people feel I am responsible for the limitations of Objective-C.
Short answer: you can't. Objective-C doesn't have the equivalent of Ruby mixins.
Slightly less short answer: Objective-C does have something with arguably the same flavour: protocols. Protocols (Interfaces in some other languages), are a way to define a set of methods an class that adopts that protocols is committing to implementing. A protocol doesn't provide an implementation though. That limitation prevents using protocols as an exact equivalent to Ruby mixins.
Even less short answer: However, the Objective-C runtime has an exposed API that lets you play with the dynamic features of the language. Then you step outside the language, but you can have protocols with default implementations (also called concrete protocols). Vladimir's answer shows one way to do that. At that point it seems to me you get Ruby mixins alright.
However, I am not sure I would recommend doing that. In most cases, other patterns fit the bill without playing games with the runtime. For example, you can have a sub-object that implement the mixed-in method (has-a instead of is-a). Playing with the runtime is OK, but has 2 drawbacks:
You make your code less readable as it requires readers to know a lot more than the language. Sure you can (and should) comment it, but remember that any necessary comment can be seen as an implementation defect.
You depend on that implementation of the language. Sure, Apple platforms are by far the most common ones for Objective-C but don't forget Cocotron or GnuStep (or Etoilé) which have different runtimes, which may or may not be compatible with Apple's on that respect.
As a side note, I state below that categories cannot add state (instance variables) to a class. By using the runtime API, you can lift that limitation too. This is beyond the scope of this answer however.
Long answer:
Two Objective-C features look like possible candidates: categories and protocols. Categories are not really the right choice here, if I understand the question properly. The right feature is a protocol.
Let me give an example. Suppose you want a bunch of your classes to have a specific ability called "sing". Then you define a protocol:
@protocol Singer
- (void) sing;
@end
Now you can declare that any of your own classes adopts the protocol the following way:
@interface Rectangle : Shape <Singer> {
<snip>
@end
@interface Car : Vehicle <Singer> {
<snip>
@end
By declaring that they adopt the protocol they commit themselves to implementing the sing
method. For example:
@implementation Rectangle
- (void) sing {
[self flashInBrightColors];
}
@end
@implementation Car
- (void) sing {
[self honk];
}
@end
Then you use those classes for example like this:
void choral(NSArray *choir) // the choir holds any kind of singer
{
id<Singer> aSinger;
for (aSinger in choir) {
[aSinger sing];
}
}
Notice that the singers in the array don't need to have a common superclass. Notice also that a class can have only one superclass, but many adopted protocols. Notice finally that type checking is done by the compiler.
In effect, the protocol mechanism is multiple inheritance used for the mixin pattern. That multiple inheritance is severely limited because a protocol cannot add new instance variables to a class. A protocol only describes a public interface adopters must implement. Unlike Ruby modules it does not contain an implementation.
That's the most of it. Let's mention categories however.
A category is declared not in angle brackets, but between parenthesis. The difference is that a category can be defined for an existing class to expand it without subclassing it. You can even do so for a system class. As you can imagine, it's possible to use categories to implement something similar to mixin. And they were used that way for a long time usually as category to NSObject
(the typical root of the inheritance hierarchy), to such an extent that they were called "informal" protocols.
It's informal because 1- no type checking is done by the compiler, and 2- implementing the protocol methods is optional.
There is no need today to use categories as protocols, especially because the formal protocols can now declare that some of their methods are optional with the keyword @optional
or required (the default) with @required
.
Categories are still useful to add some domain specific behavior to an existing class. NSString
is a common target for that.
It's also interesting to point out that most (if not all) of NSObject
facilities are in fact declared in a NSObject
protocol. This means that it's not really compelling to use NSObject
as a common superclass for all classes, though this is still commonly done for historical reasons, and well... because there is no drawback for doing so. But some system classes, such as NSProxy
, are not NSObject
.
回答2:
Shameless plug: ObjectiveMixin
It takes advantage of Objective-C runtime's capability of adding methods to a class in runtime (as opposed to categories, which are compile-time only). Check it out, it works pretty good and in a similar fashion to Ruby's mixins.
回答3:
You can literally mixin the code using #include. This is not advisable and is against all the religions in objective-c, however works perfectly.
Please, don't do it in the production code.
for example in the file:
MixinModule.header (should not be compiled or copied to the target)
-(void)hello;
MixinModule.body (should not be compiled or copied to the target)
-(void)hello{
NSLog(@"Hello");
}
in mixin class:
@interface MixinTest : NSObject
#include "MixinModule.header"
@end
@implementation MixinTest
#include "MixinModule.body"
@end
usage case:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]){
@autoreleasepool {
[[[MixinTest new] autorelease] hello];
}
return 0;
}
Please, don't do it in the production code.
回答4:
This is my take on implementing Mixins in Objective-C, without using the Objective-C runtime directly. Maybe it's helpful to someone: https://stackoverflow.com/a/19661059/171933
来源:https://stackoverflow.com/questions/2486314/does-objective-c-support-mixin-like-ruby