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

时间:2020-12-27 12:51:07

I'm working on a small hobby project of mine where I have a big structure that takes care of all core tasks. This core won't do much by itself, it needs a dozen subsystems that actually do the hard work. I've currently written only one subsystem so it's still easy to change things.

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

I have a lot of code locations where the core interfaces with the subsystems, and I don't want to have to change the core every time I add a new subsystem. My idea was to make it modular.

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

A long time I saw something similar in a game engine, where a new console command could be defined by using some preprocessor macro. That was all you had to do - after compiling it instantly worked in the game.

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

So let's take the game engine as an example for my case. I've placed comments in the code below that should make my question more obvious.

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

My question: How do I implement a modular system in Objective-C, that's built at compile time, and does not involve changing anything other than the modules themselves?

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

And now some code

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

I want to be able to add a new command with something like :

我希望能够添加一个新命令,例如:

REGISTER_COMMAND(MyClassName, "theCommand")

Remember, the above code isn't my specific case. Also, I don't want external modules, they have to be compiled as if they were implemented natively. Objective-C is fine, so is C++ or C.

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

Update
Clarification: I know how to do this with a plist file, but if I chose that I might just as well store them in my actual code. I'm looking for a C/C++/Objective-C solution that allows me to simply add the module with a preprocessor macro.

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

Update 2
Adding a bounty - I'd really like some good ideas for this.

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

7 个解决方案

#1


2  

Ok, if it must be by macro, this solution works:

好吧,如果它必须是宏,这个解决方案的工作原理:

    // 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;
}

#2


5  

I don't fully understand the problem. However, from what I can gather the sticking point is finding a hook at runtime that you can register your modules.

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

One such hook is the +(void)load class method. load is called on every class and category that is loaded. For statically link classes/categories this will be when your app starts. Even if you choice not to use Objective-C you can still create a class simply for the hook its' load method provides.

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

#3


3  

This is somewhat like whet @verec posted: You could add a special class to your project, called ModuleList. Every module wishing to register itself could do so by adding a category to ModuleList. You can put this in a macro. Using objc/runtime functions you can loop over the added properties or methods. (i.e. all properties/methods that don't originate in NSObject)

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

The benefit would be that you don't have to loop over all classes.

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

#4


2  

I'm doing something sort of along these lines in a new project I'm working on. I store information about modules (classes) in an XML file (a plist would work well too), including capabilities of the class, its name, etc. At runtime, I load the XML file, and when a particular class is needed, I instantiate it based on its name. For ease of use/good encapsulation, I have a BaseModule class from which the "modules classes" inherit. BaseModule has an initWithModuleName: method that takes a module name (as spelled out in the XML file):

在我正在开发的一个新项目中,我正在做这些事情。我将关于模块(类)的信息存储在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;
}

There's a lot more to this system than I've mentioned here and this is based on my code, but not copy/pasted from it. For one thing, I use Objective-C's ability to do method name lookup at runtime and dynamic dispatch to invoke module methods that are declared/defined in BaseModule subclasses but not in BaseModule itself. These methods are described in the XML file.

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

But the end result is that all I have to do to add a new module is create its definition in the "ModuleDefinitions.xml" file in my project, and add implementation classes for it to the project. The rest of the program will automatically pick up on its presence and start using it.

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

#5


2  

I'm adding another answer to elaborate on what was said in the chat above as this might prove useful to anyone having a similar issue.

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

The assumption this solution relies on are:

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

  • all modules and the core are part of the same executable and are compiled and linked together, like in any standard project, and
  • 所有模块和核心都是同一个可执行文件的一部分,并且被编译和链接在一起,就像在任何标准项目中一样

  • there is a naming convention between commands and module class names.
  • 命令和模块类名之间存在命名约定。

With this is mind, the following code (in "core") returns the list of all the classes in the executable whose mame match the given prefix:

考虑到这一点,下面的代码(在“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 ;
}

Now to retrieve all the classes whose name start with "PG":

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

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

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

prints:

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

Using the simple convention that commands and module class names are the same, then that's all there is to it.

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

In other words, to add a new module, just add its classes sources to the project and you're done. The runtime in core will just pick it up, provided the module "main class" name matches whatever naming convention you have established between "commands" and module class names.

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

ie, there is no need to

即,没有必要

REGISTER_COMMAND(MyClassName, "theCommand")

in any module code anymore, nor is there any need for any macro.

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

#6


1  

If we start from your interpretCommand sample code, the key structure is the allCommands dictionary.

如果我们从您的interpretCommand示例代码开始,则键结构是​​allCommands字典。

You are looking for some means to populate it such that when asked for a string (your command) it returns another string to be the class name of an objective-c class that you can then create instances of.

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

How do you want your dictionary being populated?

您希望如何填充字典?

If it is at compile time, either yourself, in some file, or some tool you write will have to write a bit of source code that inserts

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

[allCommands addObject: @"ThisNewClass" forKey: @"ThisNewCommand"] ;

If that's all you are after, then you don't even need a macro, but some kind of registry class whose sole job is to populate your dictionary with whatever new class/command pair you wan to add.

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

Something as simplistic as:

像这样简单的东西:

@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

If this is not the answer you are looking for, please could you elaborate and point out how this example doesn't precisely match your question?

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

#7


1  

aaaand.. here another solution built on load/initialize, no Macro--if you subclass Module, your command handler will be picked up at load time. You could probably make it work based on your class implementing some protocol if you wanted to...

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

#1


2  

Ok, if it must be by macro, this solution works:

好吧,如果它必须是宏,这个解决方案的工作原理:

    // 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;
}

#2


5  

I don't fully understand the problem. However, from what I can gather the sticking point is finding a hook at runtime that you can register your modules.

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

One such hook is the +(void)load class method. load is called on every class and category that is loaded. For statically link classes/categories this will be when your app starts. Even if you choice not to use Objective-C you can still create a class simply for the hook its' load method provides.

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

#3


3  

This is somewhat like whet @verec posted: You could add a special class to your project, called ModuleList. Every module wishing to register itself could do so by adding a category to ModuleList. You can put this in a macro. Using objc/runtime functions you can loop over the added properties or methods. (i.e. all properties/methods that don't originate in NSObject)

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

The benefit would be that you don't have to loop over all classes.

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

#4


2  

I'm doing something sort of along these lines in a new project I'm working on. I store information about modules (classes) in an XML file (a plist would work well too), including capabilities of the class, its name, etc. At runtime, I load the XML file, and when a particular class is needed, I instantiate it based on its name. For ease of use/good encapsulation, I have a BaseModule class from which the "modules classes" inherit. BaseModule has an initWithModuleName: method that takes a module name (as spelled out in the XML file):

在我正在开发的一个新项目中,我正在做这些事情。我将关于模块(类)的信息存储在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;
}

There's a lot more to this system than I've mentioned here and this is based on my code, but not copy/pasted from it. For one thing, I use Objective-C's ability to do method name lookup at runtime and dynamic dispatch to invoke module methods that are declared/defined in BaseModule subclasses but not in BaseModule itself. These methods are described in the XML file.

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

But the end result is that all I have to do to add a new module is create its definition in the "ModuleDefinitions.xml" file in my project, and add implementation classes for it to the project. The rest of the program will automatically pick up on its presence and start using it.

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

#5


2  

I'm adding another answer to elaborate on what was said in the chat above as this might prove useful to anyone having a similar issue.

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

The assumption this solution relies on are:

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

  • all modules and the core are part of the same executable and are compiled and linked together, like in any standard project, and
  • 所有模块和核心都是同一个可执行文件的一部分,并且被编译和链接在一起,就像在任何标准项目中一样

  • there is a naming convention between commands and module class names.
  • 命令和模块类名之间存在命名约定。

With this is mind, the following code (in "core") returns the list of all the classes in the executable whose mame match the given prefix:

考虑到这一点,下面的代码(在“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 ;
}

Now to retrieve all the classes whose name start with "PG":

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

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

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

prints:

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

Using the simple convention that commands and module class names are the same, then that's all there is to it.

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

In other words, to add a new module, just add its classes sources to the project and you're done. The runtime in core will just pick it up, provided the module "main class" name matches whatever naming convention you have established between "commands" and module class names.

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

ie, there is no need to

即,没有必要

REGISTER_COMMAND(MyClassName, "theCommand")

in any module code anymore, nor is there any need for any macro.

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

#6


1  

If we start from your interpretCommand sample code, the key structure is the allCommands dictionary.

如果我们从您的interpretCommand示例代码开始,则键结构是​​allCommands字典。

You are looking for some means to populate it such that when asked for a string (your command) it returns another string to be the class name of an objective-c class that you can then create instances of.

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

How do you want your dictionary being populated?

您希望如何填充字典?

If it is at compile time, either yourself, in some file, or some tool you write will have to write a bit of source code that inserts

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

[allCommands addObject: @"ThisNewClass" forKey: @"ThisNewCommand"] ;

If that's all you are after, then you don't even need a macro, but some kind of registry class whose sole job is to populate your dictionary with whatever new class/command pair you wan to add.

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

Something as simplistic as:

像这样简单的东西:

@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

If this is not the answer you are looking for, please could you elaborate and point out how this example doesn't precisely match your question?

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

#7


1  

aaaand.. here another solution built on load/initialize, no Macro--if you subclass Module, your command handler will be picked up at load time. You could probably make it work based on your class implementing some protocol if you wanted to...

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