Objective-C构建系统,可以做模块

时间:2011-12-30 22:37:04

标签: c++ objective-c c modularity

我正在研究我的一个小型业余爱好项目,我有一个大型结构,负责所有核心任务。这个核心本身不会做太多,它需要十几个子系统才能真正做到这一点。我目前只编写了一个子系统,所以它仍然很容易改变。

我有很多代码位置,核心与子系统接口,我不想每次添加新的子系统。我的想法是让它模块化。

很长一段时间我在游戏引擎中看到类似的东西,可以使用一些预处理器宏来定义新的控制台命令。这就是你所要做的一切 - 在编译后它立即在游戏中工作。

让我们以游戏引擎为例来说明我的情况。我在下面的代码中添加了评论,这些评论应该让我的问题更加明显。

我的问题:我如何在Objective-C中实现一个模块化系统,它是在编译时构建的,并且不涉及更改模块本身以外的任何内容?

现在有些代码

-(void)interpretCommand:(NSString*)command {
    // Find the space in the command
    NSRange pos = [command rangeOfString:@" "];
    if (pos.length == 0) return; // No space found

    NSString *theCommand = [command substringToIndex:pos.location];

    // !!! Here comes the important code !!!
    // Get all the available commands - this is what my question is about!
    NSDictionary *allCommands = nil;

    // Find the command in the dictionary
    NSString *foundCommand = [allCommands objectForKey:theCommand];

    // Execute the command
    if (foundCommand != nil) {
        [[NSClassFromString(foundCommand) new] execute];
    }
}

我希望能够使用以下内容添加新命令:

REGISTER_COMMAND(MyClassName, "theCommand")

请记住,上面的代码不是我的具体情况。此外,我不想要外部模块,它们必须被编译,好像它们是本机一样。 Objective-C很好,C ++或C也是如此。

更新
澄清:我知道如何使用plist文件执行此操作,但如果我选择了它,我也可以将它们存储在我的实际代码中。我正在寻找一个C / C ++ / Objective-C解决方案,它允许我简单地使用预处理器宏添加模块。

更新2
添加赏金 - 我真的很喜欢这个好主意。

7 个答案:

答案 0 :(得分:5)

我不完全理解这个问题。但是,从我可以收集的内容来看,关键是在运行时找到一个可以注册模块的钩子。

一个这样的钩子是+(void)load类方法。在加载的每个类和类别上调用load。对于静态链接类/类别,这将是您的应用程序启动时。即使您选择不使用Objective-C,您仍然可以仅为其load方法提供的钩子创建一个类。

答案 1 :(得分:3)

这有点像@verec发布的那样:您可以在项目中添加一个名为ModuleList的特殊类。希望注册自己的每个模块都可以通过向ModuleList添加类别来实现。你可以将它放在一个宏中。使用objc / runtime函数可以循环添加的属性或方法。 (即所有不属于NSObject的属性/方法)

好处是您不必遍历所有类。

答案 2 :(得分:2)

我正在做一些我正在研究的新项目。我将关于模块(类)的信息存储在XML文件中(plist也可以正常工作),包括类的功能,名称等。在运行时,我加载XML文件,当需要特定的类时,我根据其名称实例化它。为了便于使用/良好的封装,我有一个BaseModule类,“模块类”继承了该类。 BaseModule有一个initWithModuleName:方法,它接受一个模块名称(如XML文件中所述):

- (id)initWithModuleName:(NSString *)moduleName
{
    if ( ![self isMemberOfClass:[BaseModule class]] ) 
    {
        THROW_EXCEPTION(@"MethodToBeCalledOnBaseClassException", @"-[BaseModule initWithModuleName] must not be called on subclasses of BaseModule.");
    }

    [self release]; // always return a subclass
    self = nil;

    if ([BaseModule canInitWithModuleName:moduleName]) 
    {
        ModuleDefinition *moduleDefinition = [BaseModule moduleDefinitionForModuleName:moduleName];

        Class moduleClass = NSClassFromString(moduleDefinition.objectiveCClassName);
        self = [(BaseModule *)[moduleClass alloc] initWithModuleDefinition:moduleDefinition];
    }

    return self;
}

这个系统还有很多比我在这里提到的更多,这是基于我的代码,但不是从它复制/粘贴。首先,我使用Objective-C在运行时进行方法名称查找的能力和动态调度来调用在BaseModule子类中声明/定义但在BaseModule本身中没有的模块方法。这些方法在XML文件中描述。

但最终的结果是我要添加一个新模块所要做的就是在我的项目的“ModuleDefinitions.xml”文件中创建它的定义,并为它添加实现类。程序的其余部分将自动获取其存在并开始使用它。

答案 3 :(得分:2)

我正在添加另一个答案,详细阐述上述聊天中的内容,因为这可能对任何有类似问题的人都有用。

此解决方案依赖的假设是:

  • 所有模块和核心都是同一个可执行文件的一部分 编译和链接在一起,就像在任何标准项目中一样,
  • 命令和模块类名之间存在命名约定。

考虑到这一点,下面的代码(在“core”中)返回可执行文件中所有类的列表,其中mame与给定的前缀匹配:

#import <objc/runtime.h>

- (NSSet *) findAllClassesWithPrefix: (NSString *) prefix {
    NSMutableSet * matches = [NSMutableSet setWithCapacity:2] ;
    size_t classCount = 0 ;
    Class * classes = 0 ;

    Class nsObjectClass = objc_getClass("NSObject") ;

    classCount = (size_t) objc_getClassList(0, 0) ;

    if (classCount > 0) {
        classes = (Class *) calloc(classCount, sizeof(Class)) ;
        classCount = (size_t) objc_getClassList(classes, (int) classCount) ;

        for (int i = 0 ; i < classCount ; ++i) {
            Class  c = classes[i] ;
            if (c == nil) {
                continue ;
            } else {
                // filter out classes not descending from NSObject
                for (Class superClass = c ; superClass ; superClass = class_getSuperclass(superClass)) {
                    if (superClass == nsObjectClass) {
                        const char * cName = class_getName(c) ;
                        NSString * className = [NSString stringWithCString:cName
                                                                  encoding:NSUTF8StringEncoding] ;                        
                        if ([className hasPrefix: prefix]) {
                            [matches addObject: className] ;
                        }
                    }
                }
            }
        }

        free(classes) ;
    }

    return matches ;
}

现在检索名称以“PG”开头的所有类:

NSSet * allPGClassNames = [self findAllClassesWithPrefix:@"PG"] ;

for (NSString * string in allPGClassNames) {
    NSLog(@"found: %@", string) ;
}

打印:

2012-01-02 14:31:18.698 MidiMonitor[1167:707] found: PGMidiDestination
2012-01-02 14:31:18.701 MidiMonitor[1167:707] found: PGMidi
2012-01-02 14:31:18.704 MidiMonitor[1167:707] found: PGMidiConnection
2012-01-02 14:31:18.706 MidiMonitor[1167:707] found: PGMidiAllSources

使用命令和模块类名称相同的简单约定,那就是它的全部内容。

换句话说,要添加一个新模块,只需将其类源添加到项目中即可完成。只要模块“主类”名称与您在“命令”和模块类名称之间建立的命名约定相匹配,核心中的运行时就会选择它。

即,没有必要

REGISTER_COMMAND(MyClassName, "theCommand")

在任何模块代码中,也不需要任何宏。

答案 4 :(得分:2)

好的,如果必须是宏,那么这个解决方案就可以了:

    // the macro:
#define REGISTER_COMMAND( __THE_CLASS, __COMMAND )\
@interface __THE_CLASS##Registration @end\
@implementation __THE_CLASS##Registration \
+(void)load { [ Commands registerHandler:NSClassFromString(@""#__THE_CLASS) command:(__COMMAND) ] ; } \
@end

    // Bookkeeping/lookup class:
@interface Commands 
@end

@implementation Commands
static NSMutableDictionary * __commands = nil ;

+(void)load
{
    __commands = [[ NSMutableDictionary alloc ] init ] ;
}

+(void)registerHandler:(Class)theClass command:(NSString*)command
{
    if ( theClass && command.length > 0 )
    {
        [ __commands setObject:theClass forKey:command ] ;
    }
}

+(id)handlerForCommand:(NSString*)command
{
    Class theClass = [ __commands objectForKey:command ] ;
    return [ [ [ theClass alloc ] init ] autorelease ] ;
}

@end

    // map the command 'doit' to handler 'MyCommand', below
REGISTER_COMMAND( MyCommand, @"doit" )

    // one of our command handling objects, 'MyCommand'
@interface MyCommand : NSObject
@end

@implementation MyCommand
@end

    // test it out:
int main (int argc, const char * argv[])
{   
    @autoreleasepool 
    {
        NSLog(@"command %@ found for 'doit'\n", [ Commands handlerForCommand:@"doit" ] ) ;

    }
    return 0;
}

答案 5 :(得分:1)

如果我们从您的interpretCommand示例代码开始,那么 密钥结构是allCommands字典。

您正在寻找一些方法来填充它 当被要求输入一个字符串(你的命令)时,它会返回另一个 string作为objective-c类的类名 然后,您可以创建。

的实例

您希望如何填充字典?

如果是在编译时,要么自己,在某个文件中, 或者你写的一些工具必须写一些来源 插入

的代码
[allCommands addObject: @"ThisNewClass" forKey: @"ThisNewCommand"] ;

如果这就是你所追求的,那么你甚至不需要一个宏, 但某种注册类的唯一工作是填充 你想要的任何新的类/命令对你的字典 添加。

简单的事情:

@interface Registry : NSObject {
    NSMutableDictionary * allCommands ;
}

- (id) init ;
- (NSDictionary *) allCommands ;

@end 

@implementation Registry

- (id) init {
    if (self = [super init]) {
        allCommands = [[NSMutableDictionary alloc] initWithCapacity: 20] ;

        // add in here all your commands one by one
        [allCommands addObject: @"ThisNewClass" forKey: @"ThisNewCommand"] ;
        [allCommands addObject: @"ThisNewClass2" forKey: @"ThisNewCommand1"] ;
        [allCommands addObject: @"ThisNewClass3" forKey: @"ThisNewCommand2"] ;
        [allCommands addObject: @"ThisNewClass3" forKey: @"ThisNewCommand3"] ;
    }
    return self ;
}

- (NSDictionary *) allCommands {
    return allCommands ;
}
@end

如果这不是您正在寻找的答案,请您详细说明并说明此示例与您的问题不完全匹配?

答案 6 :(得分:1)

aaaand ..这里另一个构建在load / initialize上的解决方案,没有宏 - 如果你继承了Module,你的命令处理程序将在加载时被选中。如果你想......你可以根据你的类实现一些协议来使它工作。

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

//--------------------------------------------------------------------------------

@interface Module : NSObject

+(Module*)moduleForCommand:(NSString*)command ;
+(void)registerModuleClass:(Class)theClass forCommand:(NSString*)command ;


+(NSString*)command ;   // override this to returnthe command your class wants to handle

@end

@implementation Module

static NSMutableDictionary * __modules = nil ;
+(void)load
{
    @autoreleasepool 
    {
        __modules = [[ NSMutableDictionary alloc ] init ] ;
    }
}

+(void)initialize
{
    [ super initialize ] ;
    if ( self == [ Module class ] )
    {
        unsigned int count = 0 ;        
        Class * classList = objc_copyClassList( & count ) ;
        for( int index=0; index < count; ++index )
        {
            Class theClass = classList[ index ] ;
            if ( class_getSuperclass( theClass ) == self )
            {
                [ Module registerModuleClass:theClass forCommand:[ theClass command ] ] ;
            }
        }
    }
}

+(Module*)moduleForCommand:(NSString*)command
{
    Class theClass = [ __modules objectForKey:command ] ;
    return !theClass ? nil : [ [ [ theClass alloc ] init ] autorelease ] ;
}

+(void)registerModuleClass:(Class)theClass forCommand:(NSString*)command
{
    [ __modules setObject:theClass forKey:command ] ;
}

+(NSString *)command
{
    NSLog(@"override +command in your Module subclass!\n") ;
    return nil ;
}
+(BOOL)shouldLoad
{
    return YES ;    // override and set to NO to skip this command during discovery
}

@end

//--------------------------------------------------------------------------------

@interface MyModule : Module 
@end
@implementation MyModule

+(NSString *)command
{
    return @"DoSomething" ;
}
@end

//--------------------------------------------------------------------------------
int main (int argc, const char * argv[])
{
    @autoreleasepool 
    {       
        Module * m = [ Module moduleForCommand:@"DoSomething" ] ;
        NSLog( @"module for command 'DoSomething': found %@\n", m ) ;

    }
    return 0;
}