Skip to content

Ruby-like mixin functionality for Objective-C programs.

Notifications You must be signed in to change notification settings

sandosh/ObjectiveMixin

 
 

Repository files navigation

ObjectiveMixin

ObjectiveMixin allows Objective-C classes to receive additional functionality at runtime, similar to the way Ruby mixins work. You can extend a class using methods defined in some other class, and gain that class's functionality for free. Since Objective-C doesn't support multiple inheritance, this can be quite useful at times when subclassing isn't an option and protocols don't do exactly what you want.

This works by taking advantage of the Objective-C runtime environment and its ability to add methods to an existing class while the program is executing. By specifying source and destination classes when performing a "mixing" operation, the destination class gains all of the source class's method implementations.

There's a small limitation due to the fact that instance variables cannot be added after the class pair is created (see Apple docs for class_addIvar). Thus, the source class's method implementations must not contain direct references to instance variables. This can be easily circumvented by accessing ivars through properties instead of accessing them directly (i.e. self.someValue instead of someValue). The runtime will then synthesize the property in the destination instance (instance, not class!) and will work normally.

Usage

  1. Copy Mixin.h and Mixin.m into your project.
  2. #import "Mixin.h"
  3. Have fun.

Basic Example

Say you have a source class that looks like this:

@interface Ninja : NSObject {
	NSString* clanName;
}

@property (nonatomic, retain) NSString* clanName;

- (void) doNinjaStuff;

@end

@implementation Ninja
@synthesize clanName;

- (void) doNinjaStuff {
	self.clanName = @"Iga";
	NSLog(@"I'm a %@ and my clan name is %@", [[self class] description], self.clanName);
}

@end

If you then use Mixin to extend Turtle with Ninja, you get this:

Turtle* turtle = [[Turtle alloc] init];
[Mixin from:[Ninja class] into:[Turtle class]];
[(id)turtle doNinjaStuff];	// Prints "I'm a Turtle and my clan name is Iga"

Even though the clanName instance variable does not exist in Turtle's definition, it is synthesized at runtime in the turtle object, and everything works fine.

Note that in order to avoid "object may not respond to selector" compiler warnings you have to cast the turtle variable to the id type (or alternatively, to Ninja, but id is cleaner in my opinion). Read the next section to see how to avoid this.

You can also use [Mixin from:[Ninja class] into:[Turtle class] followInheritance:YES]; to mix in all Ninja's inherited methods, up to the common ancestor of Ninja and Turtle. In other words, if we have the following inheritance trees: Ninja -> Human -> NSObject and Turtle -> NSObject, then the from:into:followInheritance: method will mix in methods from both Ninja and Human into Turtle, but not from NSObject since it's their common ancestor (they already inherit its methods).

There's also a convenient category defined on top of NSObject (it's defined in Mixin.h as well), so you can use it instead of calling Mixin class methods:

Turtle* turtle = [[turtle alloc] init];
[turtle mixinFrom:[Ninja class]];	// Or mixinFrom:followInheritance:
[(id)turtle doNinjaStuff];	// Prints "I'm a Turtle and my clan name is Iga"

Be aware that this is just syntactic sugar - even though the method is called on an object instance, it still extends the class itself. I actually prefer using the Mixin class, since it is more clear by reading the code that you're extending the class functionality, not just the functionality of a single object.

More Subtle Example

Take a look at Examples/Serializable.h. There's a Serializable class there which you mix into your class at runtime, and there's a SerializableMixin protocol which your class should implement.

Methods that are marked as @required have to be implemented by your class, and methods that are marked as @optional will be mixed in from the Serializable class at runtime. The optional methods are defined in the protocol in order to avoid "object may not respond to selector" compiler warnings.

Now take a look at ObjectiveMixinExamples/MountainBike.h. This class complies with the SerializableMixin protocol, by implementing the two required methods from that protocol. In ObjectiveMixinExamples/MountainBike.m you can see that the Serializable class is mixed in in the initialize class method, which is invoked by the Obj-C runtime only once at class creation time. This way you're free from worrying about whether you already mixed in a certain class - this code is guaranteed to run only once.

(This approach was suggested to me by Benedict Cohen (@benedictC), thanks man!)

You should also check out the Singleton example in Examples/Singleton.h, I use that one a lot.

Why?

This is just another mechanism you can use when designing your class hierarchy, in addition to the usual Objective-C design patterns. For example, you can have an algorithm that is used by multiple classes and you don't want to "pollute" their ancestor by extending it with a category. Plus, this is a runtime feature so it opens a lot of doors for experimentation. Feel free to read up on some Ruby mixin usage examples and implement them in Obj-C.

Fun!

About

Ruby-like mixin functionality for Objective-C programs.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published