26

在 Ruby 中,有模块,您可以通过“混入”模块来扩展类。

module MyModule
  def printone
    print "one" 
  end
end

class MyClass
  include MyModule
end

theOne = MyClass.new
theOne.printone 
>> one

在 Objective-C 中,我发现我有一组常用方法,我希望一些 Class 可以“继承”。在不创建公共类并从该公共类派生所有内容的情况下,我还有什么其他方法可以实现这一点?

4

4 回答 4

31

无耻插件:ObjectiveMixin

它利用了 Objective-C 运行时在运行时向类添加方法的能力(与仅在编译时的类别相反)。检查一下,它工作得很好,并且与 Ruby 的 mixin 类似。

于 2011-04-12T09:07:05.323 回答
28

编辑:添加更改是因为有些人认为我应对 Objective-C 的局限性负责。

简短的回答:你不能。Objective-C 没有 Ruby mixins 的等价物。

稍微简短的回答:Objective-C 确实有一些可以说是相同风格的东西:协议。协议(某些其他语言中的接口)是一种定义一组方法的方法,该类采用该协议致力于实现。但是,协议不提供实现。该限制阻止了将协议用作与 Ruby mixin 完全等效的协议。

更简短的回答:然而,Objective-C 运行时有一个公开的 API,让您可以使用该语言的动态特性。然后你会跳出语言,但你可以拥有带有默认实现的协议(也称为具体协议)。弗拉基米尔的回答显示了一种方法。在那一点上,在我看来,你可以得到 Ruby mixins。

但是,我不确定我是否会建议这样做。在大多数情况下,其他模式可以满足要求,而无需与运行时玩游戏。例如,您可以有一个实现混合方法的子对象(has-a而不是is-a)。使用运行时是可以的,但有两个缺点:

  • 您使代码的可读性降低,因为它要求读者比语言了解更多。当然你可以(并且应该)评论它,但请记住,任何必要的评论都可以被视为实现缺陷。

  • 您依赖于语言的实现。当然,Apple 平台是目前最常见的 Objective-C 平台,但不要忘记 Cocotron 或 GnuStep(或 Etoilé),它们具有不同的运行时,在这方面可能与 Apple 兼容,也可能不兼容。

作为旁注,我在下面声明类别不能将状态(实例变量)添加到类。通过使用运行时 API,您也可以解除该限制。然而,这超出了这个答案的范围。

长答案:

两个 Objective-C 特性看起来像是可能的候选者:类别和协议。如果我正确理解了这个问题,那么类别在这里并不是真正的正确选择。正确的功能是协议。

让我举个例子吧。假设你想让你的一些班级拥有一种叫做“唱歌”的特殊能力。然后定义一个协议:

@protocol Singer
    - (void) sing;
@end

现在您可以通过以下方式声明您自己的任何类都采用该协议:

@interface Rectangle : Shape <Singer> {
    <snip>
@end

@interface Car : Vehicle <Singer> {
    <snip>
@end

通过声明他们采用协议,他们承诺实现该sing方法。例如:

@implementation Rectangle

- (void) sing {
    [self flashInBrightColors];
}

@end

@implementation Car

- (void) sing {
    [self honk];
}

@end

然后,您可以像这样使用这些类:

void choral(NSArray *choir) // the choir holds any kind of singer
{
    id<Singer> aSinger;
    for (aSinger in choir) {
        [aSinger sing];
    }
}

请注意,数组中的歌手不需要有一个共同的超类。还要注意,一个类只能有一个超类,但可以有许多采用的协议。最后请注意,类型检查是由编译器完成的。

实际上,协议机制是用于混合模式的多重继承。多重继承受到严重限制,因为协议不能向类添加新的实例变量。协议仅描述采用者必须实现的公共接口。与 Ruby 模块不同,它不包含实现。

这是最重要的。然而,让我们提到类别。

类别不是在尖括号中,而是在括号之间声明。不同之处在于,可以为现有类定义一个类别来扩展它,而无需对其进行子类化。您甚至可以为系统类这样做。可以想象,可以使用类别来实现类似于 mixin 的东西。并且它们以这种方式使用了很长时间,通常作为类别NSObject(继承层次结构的典型根),以至于它们被称为“非正式”协议。

这是非正式的,因为 1- 编译器不进行类型检查,2- 实现协议方法是可选的。

今天没有必要使用类别作为协议,特别是因为正式协议现在可以声明它们的一些方法是可选的,关键字是可选的,@optional或者是必需的(默认)@required

类别对于向现有类添加一些特定于域的行为仍然很有用。NSString是一个共同的目标。

有趣的是,大多数(如果不是全部)NSObject设施实际上是在NSObject协议中声明的。这意味着将其NSObject用作所有类的公共超类并不是很有吸引力,尽管出于历史原因仍然通常这样做,而且......因为这样做没有缺点。但是某些系统类,例如NSProxy不是 NSObject

于 2010-03-21T09:26:33.850 回答
11

您可以使用#include 从字面上混合代码。这是不可取的,并且违反了objective-c中的所有宗教,但是效果很好。

请不要在生产代码中这样做。

例如在文件中:

MixinModule.header (不应编译或复制到目标)

-(void)hello;

MixinModule.body (不应编译或复制到目标)

-(void)hello{
    NSLog(@"Hello");
}

在混合类中:

@interface MixinTest : NSObject
#include "MixinModule.header"
@end

@implementation MixinTest
#include "MixinModule.body"
@end

使用案例:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]){
    @autoreleasepool {
        [[[MixinTest new] autorelease] hello];
    }
    return 0;
}

请不要在生产代码中这样做。

于 2012-07-20T21:06:08.580 回答
1

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

于 2013-12-23T21:54:29.763 回答