1

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

但是,我想要只使用一个类的代码,并且我想避免重复#ifdefs。

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

  • 通用类
  • iOS 特定类
  • OSXSpecificClass

iOSSpecificClass并且OSXSpecificClass都扩展了 UniversalClass。所有调用都将在 UniversalClass 上完成,该类负责调用 和 的相应iOSSpecificClass方法OSXSpecificClass

我想出了两个解决方案:

@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

这种方法的问题是我必须对我添加的每个方法进行更改,我觉得我有点重复自己而不需要。

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

4

5 回答 5

2

一种选择是为两个目标(一个用于 OSX,另一个用于 iOS)拥有一个通用的头文件和两个不同的实现,它们都导入和实现头方法。

像这样的东西:

在此处输入图像描述

于 2014-02-28T14:49:05.610 回答
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

// 测试

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 在另一个);或三个,如果您要拥有两者共享的共同例程。

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

于 2014-02-28T16:05:25.597 回答
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 上做出的承诺,并且只获得一次正确的类。

于 2014-02-28T14:42:07.720 回答
0
于 2014-02-28T20:39:59.637 回答
0

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

我的类.h

@interface MyClass : NSObject

- (void)someMethod;

@end

我的班级.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

希望有帮助!

于 2014-03-01T20:15:38.467 回答