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

时间:2014-02-28 14:34:08

标签: objective-c architecture message-forwarding

我希望能够有两个负责响应选择器的类,具体取决于平台是iOS还是OSX。

但是,我希望代码只使用一个类,我想避免重复#ifdefs。

理想情况下,我希望有3个班级:

  • UniversalClass
  • iOSSpecificClass
  • OSXSpecificClass

iOSSpecificClassOSXSpecificClass都扩展了UniversalClass。 所有调用都将在UniversalClass中完成,该类负责调用iOSSpecificClassOSXSpecificClass的相应方法。

我提出了两种解决方案:

@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中可以提供或不能提供的内容。警告也告诉我们。格儿。警告。

第二种方法是这样的:

@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

这种方法的问题在于我必须为我添加的每个方法执行更改,并且我觉得我有点重复自己而不需要。

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

5 个答案:

答案 0 :(得分:2)

一个选项是为两个目标创建一个公共头文件和两个不同的实现(一个用于OSX,另一个用于iOS),它们都导入并实现头方法。

这样的事情:

enter image description here

答案 1 :(得分:1)

另一种方法是检查你是否真的需要两个班级。一个@interface和两个@implementations(可能在单独的文件中)是我见过的模式。

类似的东西(来自CodeRunner,我在那里进行了测试):

#import <Foundation/Foundation.h>

// #define iPHONE 1

@interface MyClass : NSObject

- (NSString*) someString;

- (BOOL) aMethod: (NSString*) inString;

@end



// common implementations here
@interface MyClass (common)
- (NSString*) commonString;
@end

@implementation MyClass (common)

- (NSString*) commonString
{
    return @"same";
}
@end

#ifdef iPHONE

// iPhone specific implementations
@implementation MyClass

- (BOOL) aMethod: (NSString*) inString
{
    return [inString isEqualToString: @"iPhone Impl"];
}

- (NSString*) someString
{
return @"iPhone Impl";
}

@end

#else

@implementation MyClass

- (BOOL) aMethod: (NSString*) inString
{
    return [inString isEqualToString: @"iPhone Impl"];
}

- (NSString*) someString
{
return @"OS X Impl";
}

@end

#endif

// test

int main(int argc, char *argv[]) {
    @autoreleasepool {
    MyClass * obj = [[MyClass alloc] init];

    NSLog(@"is iPhone? %@", [obj aMethod: [obj someString]] ? @"YES" : @"NO");
    NSLog( @"string: %@", [obj someString] );
}
}

显然,你可以通过拥有两个.m文件并在每个文件中放置一个实现(iPhone在一个,OS X在另一个中)来更优雅地做到这一点;如果你打算有两个共同的惯例,那就是三个。

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

答案 2 :(得分:0)

你可以选择这样的东西:

@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];
}

这将通过实施相应的方法(无警告)来保留您对.h的承诺,并且只获得一次正确的类。

答案 3 :(得分:0)

如果忽略forwardingTargetForSelector:版本特定情况的警告怎么样?这就像说“嘿,我知道我在做什么!”: - )

#pragma行周围添加类似@implementation次调用的内容:

...

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

...

请参阅此answer here on Stack Overflow

答案 4 :(得分:0)

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

MyClass.h

@interface MyClass : NSObject

- (void)someMethod;

@end

MyClass.m

@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个独立类组成的集群是多余的。

此解决方案与https://stackoverflow.com/users/145108/dadhttps://stackoverflow.com/users/3365314/miguel-ferreira建议的解决方案相比没有任何好处,因为我们仍然需要对导入代码进行分支:

#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

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

MyClass.h
MyClass.m
MyClass_Private_iOS.m
MyClass_Private_Mac.m

希望有所帮助!