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

Objective-C build system that can do modules

本文关键字:模块 构建 系统 Objective-C      更新时间:2023-10-16

我正在做一个小的爱好项目,我有一个大的结构来处理所有的核心任务。这个核心本身不会做很多事情,它需要一打子系统来完成艰苦的工作。我目前只写了一个子系统,所以修改东西还是很容易的。

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

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

让我们以游戏引擎为例。我在下面的代码中放置了注释,使我的问题更明显。

我的问题:我如何在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
添加赏金-我真的想要一些好主意。

我不完全明白这个问题。然而,从我收集到的关键点是在运行时找到一个可以注册模块的钩子。

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

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

这样做的好处是不必遍历所有类。

好吧,如果必须是宏,这个解决方案是有效的:

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

我正在一个新项目中做一些类似于这些行的事情。我将关于模块(类)的信息存储在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"文件中创建它的定义,并为它添加实现类到项目中。程序的其余部分将自动发现它的存在并开始使用它。

如果我们从interpretCommand示例代码开始键结构为allCommands字典。

你正在寻找一些方法来填充它,这样当请求一个字符串(你的命令)时,它返回另一个字符串作为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

如果这不是你想要的答案,请详细说明并指出这个例子与你的问题不完全匹配?

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

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

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

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

#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

使用命令和模块类名相同的简单约定,那么它就只有这些了。

换句话说,要添加一个新模块,只需将其类源添加到项目中,就完成了。如果模块的"主类"名称与你在"命令"和模块类名称之间建立的任何命名约定匹配,core中的运行时将会拾取它。

也就是说,没有必要

REGISTER_COMMAND(MyClassName, "theCommand")

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

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