我正在研究我的一个小型业余爱好项目,我有一个大型结构,负责所有核心任务。这个核心本身不会做太多,它需要十几个子系统才能真正做到这一点。我目前只编写了一个子系统,所以它仍然很容易改变。
我有很多代码位置,核心与子系统接口,我不想每次添加新的子系统。我的想法是让它模块化。
很长一段时间我在游戏引擎中看到类似的东西,可以使用一些预处理器宏来定义新的控制台命令。这就是你所要做的一切 - 在编译后它立即在游戏中工作。
让我们以游戏引擎为例来说明我的情况。我在下面的代码中添加了评论,这些评论应该让我的问题更加明显。
我的问题:我如何在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
添加赏金 - 我真的很喜欢这个好主意。
答案 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;
}