ObjectiveMixin 允许 Objective-C 类在运行时接收额外的功能,类似于 Ruby mixins 的工作方式。您可以使用在某个其他类中定义的方法扩展一个类,并免费获得该类的功能。由于 Objective-C 不支持多重继承,因此在某些情况下,当子类化不是一个选择且协议无法完全满足您的需求时,这可能会非常有用。
这是通过利用 Objective-C 运行环境以及它能够在程序执行期间向现有类添加方法的能力来实现的。通过在“混合”操作中指定源和目标类,目标类将获得源类所有方法实现。
由于实例变量在创建类对之后无法添加(请参考 Apple 文档 中的 class_addIvar
),因此存在一个小的限制。因此,源类的方
Mixin.h
和 Mixin.m
复制到您的项目中。#import "Mixin.h"
假设您有一个如同以下所示的源类
@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
如果您使用 Mixin
将 Ninja
扩展到 Turtle
,则得到如下所示
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"
即使 clanName
实例变量并未定义在 Turtle
的定义中,它也在运行时合成在 turtle
对象中,并且一切正常运行。
请注意,为了避免 "对象可能无响应于选择器" 的编译器警告,您必须将 turtle
变量转换为 id
类型(或者选择性地将其转换为 Ninja
,但我认为 id
更干净)。下一节将介绍如何避免这种情况。
您还可以使用 [Mixin from:[Ninja class] into:[Turtle class] followInheritance:YES];
将 Ninja
的所有继承方法混合到 Turtle
中,直到 Ninja
和 Turtle
的共同祖先。换句话说,如果我们有以下继承树:Ninja -> Human -> NSObject
和 Turtle -> NSObject
,则 from:into:followInheritance:
方法将将 Ninja
和 Human
的方法混合到 Turtle
中,但不从 NSObject
混合,因为它是它们的共同祖先(它们已经从其继承了方法)。
此外,还有一个定义在 NSObject
之上的方便分类(同样也在 Mixin.h
中定义),因此您可以代替调用 Mixin
类方法来使用它。
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"
请注意,这只是语法糖 - 虽然方法是通过对对象实例调用来实现的,但它仍然扩展了类本身。实际上,我更喜欢使用 Mixin
类,因为通过阅读代码你可以更清楚地知道你是在扩展类功能,而不是单个对象的功能。
最后,如果您想要混合进来的类已经实现了一些混合方法,可以在 from:to:followInheritance:force
中传递 NO
作为强制参数,以保留它们的原始实现。否则,所有方法都将被 Mixin 方法覆盖。
看看 Examples/Serializable.h
。那里有一个 Serializable
类,您可以在运行时将其混合到您的类中,还有一个您的类应该实现的 SerializableMixin
协议。
标记为 @required
的方法必须由您的类实现,标记为 @optional
的方法将在运行时从 Serializable
类中混合进来。定义可选方法是为了避免编译器警告“对象可能没有响应选择器”。
现在看看 ObjectiveMixinExamples/MountainBike.h
。这个类通过实现该协议的两个必要方法,遵循 SerializableMixin
协议。在 ObjectiveMixinExamples/MountainBike.m
中,您可以看到 Serializable
类在 initialize
类方法中被混合进来,该方法由 Objective-C 运行时在类创建时间仅调用一次。这样,您可以不必担心是否已混合某个类 - 这段代码保证只运行一次。
(此方法是由 Benedict Cohen (@benedictC) 建议给我,谢谢!)
您还应该检查 Examples/Singleton.h
中的 Singleton
示例,我经常使用这个。
现在,您现在也可以在运行时动态创建子类。在某些方面,这与在协议中创建具体方法类似。
例如,您可能会声明一个 UIView 混合,它存储将被 用于代替 -drawRect: 的块,如下所示
@protocol THDrawRectWithBlocks <NSObject>
@property (nonatomic, copy) void(^drawRectBlock)(UIView *self, CGRect rect);
@end
@interface THDrawRectWithBlocksMixin : UIView <THDrawRectWithBlocks>
@end
实现方式如下所示
@implementation THDrawRectWithBlocksMixin
@synthesize drawRectBlock;
- (void)drawRect:(CGRect)rect
{
self.drawRectBlock(self, rect);
}
@end
然后,您可以使用 -allocWithSuperclass: 方法来实例化对象。例如,这将创建一个具有您 THDrawRectWithBlocks 类方法的 UIButton
UIButton<THDrawRectWithBlocks> *button = [(UIButton<THDrawRectWithBlocks> *)[THDrawRectWithBlocksMixin allocWithSuperclass:[UIButton class]] initWithFrame:self.view.bounds];
button.drawRectBlock = ^(UIView *view, CGRect rect) {
[[UIColor redColor] set];
UIRectFill(rect);
};
因为是“真实”子类,尽管是创建在运行时,因此您可以在混合类中声明属性甚至 ivars(如上面的示例所示)。
如果您将类名后缀命名为“Mixin”,而将协议后缀省略(如本示例所示),Objective Mixin 将确保动态创建的类正确标记为符合该协议(因此,在本例中,对象会对 [button conformsToProtocol:@protocol(THDrawRectWithBlocks)] 返回 YES)。
请注意,在您的混合代码中调用 'super',在运行时将调用 动态父类的方法(因此在这个例子中,将调用 UIButton 的方法,而不是 UIView 的方法)。
-allocWithSuperclass: 的便利方法不足以允许多于一个动态父类,但您可以使用更通用的 -classWithSuperclass: 方法来“链式”应用混合,如下所示
[[MySecondUIViewMixin classWithSuperclass:[MyFirstUIViewMixin classWithSuperclass:UIView] alloc] init];
ObjectiveMixin 是一种在构建类层次时可以使用的机制,这除了常规的 Objective-C 设计模式之外。例如,你可以有一个由多个类使用的算法,同时你不想通过扩展类别来“污染”它们的祖先。此外,这是一个运行时特性,因此为实验打开了许多可能性。请随意阅读一些 Ruby 混入使用示例并在 Objective-C 中实现它们。
有趣!