Objective c 定义根据平台使用不同类的类

Objective c 定义根据平台使用不同类的类,objective-c,architecture,message-forwarding,Objective C,Architecture,Message Forwarding,我希望能够有两个类负责根据平台是iOS还是OSX对选择器做出不同的响应 但是,我希望代码只使用一个类,并且我希望避免重复ifdef 理想情况下,我希望有3门课: 通用类 特定类别 OSXSpecificClass iOSSpecificClass和osxsSpecificClass都扩展了UniversalClass。 所有调用都将执行到UniversalClass,该类负责调用iOSSpecificClass和osxsSpecificClass的相应方法 我提出了两种解决方案: @inte

我希望能够有两个类负责根据平台是iOS还是OSX对选择器做出不同的响应

但是,我希望代码只使用一个类,并且我希望避免重复ifdef

理想情况下,我希望有3门课:

  • 通用类
  • 特定类别
  • OSXSpecificClass
iOSSpecificClass
osxsSpecificClass
都扩展了UniversalClass。 所有调用都将执行到UniversalClass,该类负责调用
iOSSpecificClass
osxsSpecificClass
的相应方法

我提出了两种解决方案:

@interface UniversalClass : NSObject

+ (void) universalMethod;

@end

@implementation UniversalClass

+(id)forwardingTargetForSelector:(SEL)aSelector {
    #if TARGET_OS_IPHONE
        return [iOSSpecificClass class];
    #else
        return [OSXSpecificClass class];
    #endif
}

@end
这种方法的问题在于,
UniversalClass
在.h中承诺了一些可以或不能实现的东西。这些警告也告诉我们。Grr。警告

第二种方法是这样的:

@implementation UniversalClass

+ (Class)correctClass {
    Class aClass = Nil;

    #if TARGET_OS_IPHONE
        aClass = [iOSSpecificClass class];
    #else
        aClass = [OSXSpecificClass class];
    #endif

    return aClass;
}

+ (void)universalMethod {
    Class masterClass = [UniversalClass correctClass];
    [masterClass universalMethod];
}
@end
@implementation UniversalClass

static Class class;

+ (void)load
{
  class = [UniversalClass correctClass];
}

+ (Class)correctClass {
    Class aClass = Nil;

    #if TARGET_OS_IPHONE
        aClass = [iOSSpecificClass class];
    #else
        aClass = [OSXSpecificClass class];
    #endif

    return aClass;
}

+ (void)universalMethod {
    [class universalMethod];
}
MyClass.h
MyClass.m
MyClass_Private_iOS.m
MyClass_Private_Mac.m
这种方法的问题是,我必须对我添加的每个方法进行更改,我觉得我有点在重复自己,而不需要


在这两种解决方案中,我必须注意哪些边缘情况?有比这些更好的解决方案吗?

您可以采用以下方法:

@implementation UniversalClass

+ (Class)correctClass {
    Class aClass = Nil;

    #if TARGET_OS_IPHONE
        aClass = [iOSSpecificClass class];
    #else
        aClass = [OSXSpecificClass class];
    #endif

    return aClass;
}

+ (void)universalMethod {
    Class masterClass = [UniversalClass correctClass];
    [masterClass universalMethod];
}
@end
@implementation UniversalClass

static Class class;

+ (void)load
{
  class = [UniversalClass correctClass];
}

+ (Class)correctClass {
    Class aClass = Nil;

    #if TARGET_OS_IPHONE
        aClass = [iOSSpecificClass class];
    #else
        aClass = [OSXSpecificClass class];
    #endif

    return aClass;
}

+ (void)universalMethod {
    [class universalMethod];
}
MyClass.h
MyClass.m
MyClass_Private_iOS.m
MyClass_Private_Mac.m

这将通过实现相应的方法(无警告)来遵守您在.h上所做的承诺,并且只获得一次正确的类

一个选项是为导入和实现头方法的两个目标(一个用于OSX,另一个用于iOS)提供一个公共头文件和两个不同的实现

大概是这样的:

@implementation UniversalClass

+ (Class)correctClass {
    Class aClass = Nil;

    #if TARGET_OS_IPHONE
        aClass = [iOSSpecificClass class];
    #else
        aClass = [OSXSpecificClass class];
    #endif

    return aClass;
}

+ (void)universalMethod {
    Class masterClass = [UniversalClass correctClass];
    [masterClass universalMethod];
}
@end
@implementation UniversalClass

static Class class;

+ (void)load
{
  class = [UniversalClass correctClass];
}

+ (Class)correctClass {
    Class aClass = Nil;

    #if TARGET_OS_IPHONE
        aClass = [iOSSpecificClass class];
    #else
        aClass = [OSXSpecificClass class];
    #endif

    return aClass;
}

+ (void)universalMethod {
    [class universalMethod];
}
MyClass.h
MyClass.m
MyClass_Private_iOS.m
MyClass_Private_Mac.m

另一种选择是检查您是否真的需要两个类。一个@接口和两个@实现(可能在单独的文件中)是我见过的一种模式

类似于(这是我测试的CodeRunner中的内容):

显然,您可以通过拥有两个.m文件并在每个文件中放置一个实现(一个是iPhone,另一个是OSX)来更优雅地实现这一点;或者三个,如果你有共同的例程,由双方共享


无论如何,这只是获得相同/相似效果的另一种方法-使用单一界面实现不同的功能。

忽略针对您的
转发目标的特定情况的警告,如何选择:
版本?就像说“嘿,我知道我在做什么!”:-)

@implementation
行周围添加类似于
#pragma
的调用:

...

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation UniversalClass
#pragma clang diagnostic pop

...

参见此。

您提出的解决方案是类集群模式,这在Cocoa中非常常见(例如,它用于NSArray、NSValue等)。类簇是从构造函数返回私有子类而不是请求的类的实例的类。在这种情况下,您可以通过以下方式实现:

MyClass.h

@interface MyClass : NSObject

- (void)someMethod;

@end
我的班级

@implementation MyClass

+ (id)alloc
{
    if (self == [MyClass class])
    {
        #if TARGET_OS_IPHONE
            return [MyClass_iOS alloc];      
        #else
            return [MyClass_Mac alloc];
        #endif
    }
    else
    {
        return [super alloc];
    }
}

- (void)someMethod
{
    //abstract, will be overridden
}  

@end
MyClass_iOS和MyClass_Mac将在单独的文件中声明,并私下导入到McClass.m文件中

乍一看,这似乎是一个相当优雅的解决方案,但并不适合这种情况。类集群非常适合在编译时不知道需要哪个实现的情况下在运行时交换类实现(很好的例子是支持不同的iOS版本,或者在iPad和iPhone上表现不同的通用应用程序),但对于Mac/iOS,我们在编译时知道需要哪些代码,因此,引入一个由3个独立类组成的集群是多余的

与或建议的解决方案相比,此解决方案实际上没有提供任何好处,因为我们仍然需要分支导入代码:

#if TARGET_OS_IPHONE
    #import "MyClass_iOS.h"
#else
    #import "MyClass_Mac.h"
#endif
我们可以通过为MyClass_iOS和MyClass_Mac(Miguel的解决方案)都提供一个标头,或者将两个实现都放在同一个文件中(这是Dad的解决方案)来解决这个问题,但我们只是在您已经拒绝的解决方案之一的基础上构建了一个层

就我个人而言,我只会使用一个.m文件,其中包含三个清晰划分的部分:

@interface MyClass

#pragma mark -
#pragma mark Common code

- (void)someMethod1
{

}

#pragma mark -
#pragma mark iOS code
#if TARGET_OS_IPHONE

- (void)someMethod2
{

}

#pragma mark -
#pragma mark Mac code
#else

- (void)someMethod2
{

}

#endif

@end
这避免了创建不必要的类,并使您可以轻松地为每个平台创建共享方法或单独的实现,而无需在接口中公开这些方法或实现

如果这两个平台的类肯定没有任何共同的代码,我可能会选择Miguel的解决方案,它非常干净

我不接受“用户混淆”的解释。您基本上有以下三个文件:

MyClass.h
MyClass_iOS.m
MyClass_Mac.m
我认为如果有人对这意味着什么感到困惑,他们不应该在您的代码库上工作;-)

如果您确实希望继承两个平台之间的共享代码,那么您还可以将其与类集群方法相结合,在这种情况下,MyClass.m文件将同时包含共享实现和私有接口:

@interface MyClass_Private : MyClass

- (void)somePlatformSpecificMethod;

@end

@implementation MyClass

+ (id)alloc
{
    if (self == [MyClass class])
    {
        return [MyClass_Private alloc];      
    }
    else
    {
        return [super alloc];
    }
}

- (void)someSharedMethod
{
    //concrete implementation
}

@end
您的项目结构看起来更像这样:

@implementation UniversalClass

+ (Class)correctClass {
    Class aClass = Nil;

    #if TARGET_OS_IPHONE
        aClass = [iOSSpecificClass class];
    #else
        aClass = [OSXSpecificClass class];
    #endif

    return aClass;
}

+ (void)universalMethod {
    Class masterClass = [UniversalClass correctClass];
    [masterClass universalMethod];
}
@end
@implementation UniversalClass

static Class class;

+ (void)load
{
  class = [UniversalClass correctClass];
}

+ (Class)correctClass {
    Class aClass = Nil;

    #if TARGET_OS_IPHONE
        aClass = [iOSSpecificClass class];
    #else
        aClass = [OSXSpecificClass class];
    #endif

    return aClass;
}

+ (void)universalMethod {
    [class universalMethod];
}
MyClass.h
MyClass.m
MyClass_Private_iOS.m
MyClass_Private_Mac.m

希望有帮助

这难道不能用工厂模式和依赖注入来解决吗?@AndréBarbosa重点是展示一个具体的解决方案,而不是一个抽象的解决方案。@AndréBarbosa注意到,在这种情况下,我们只使用类方法。如果您有针对这种特定情况的解决方案,请随意给出:)我不会依赖target并向其添加类。对于可能加入项目的新开发人员来说,这是最基本的困惑。如果你想创建逻辑,把它放在实际的代码上。好吧,但这里的要点是没有逻辑。编写所有的平台连接逻辑是一件没有意义的事情,因为你会弄乱你的代码,特别是当IDE为你这样做的时候。使用pragmas使编译器沉默就像给饥饿的婴儿下药:p这是我试图避免的解决方案之一:/。所以杂乱无章:/只有当你像我在CodeRunner中做测试那样把它们都放在一个文件中时才会变得杂乱无章。将这两个不同的实现放在不同的文件中,它本质上变成了两个具有公共头文件的类