Objective-C构建系统,可以做模块
Objective-C build system that can do modules
我正在做一个小的爱好项目,我有一个大的结构来处理所有的核心任务。这个核心本身不会做很多事情,它需要一打子系统来完成艰苦的工作。我目前只写了一个子系统,所以修改东西还是很容易的。
我有许多代码位置,其中核心与子系统接口,并且我不想每次添加新的子系统时都必须更改核心。我的想法是让它模块化。
很长一段时间以来,我在游戏引擎中看到了类似的东西,其中新的控制台命令可以通过使用一些预处理器宏来定义。这就是你所要做的——编译后它立即在游戏中运行。
让我们以游戏引擎为例。我在下面的代码中放置了注释,使我的问题更明显。
我的问题:我如何在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;
}
- 从 pybind11 构建导入模块时出现"分段错误(核心转储)"
- 通过 cmake 从C++扩展构建 Python 子模块
- 使用 task_group 的英特尔线程构建模块性能不佳(新用户)
- 什么是运行英特尔线程构建模块的 XCode 8 环境变量
- Qt 模块(QtSerialPort)在Qt定制构建中找不到(针对ARM交叉编译)
- 无法在 Linux 上构建opencv_contrib模块
- 使用加密模块构建 Poco 1.9.0
- 交叉编译Qt模块(不构建完全全新构建)并添加到现有Qt中
- 如何导入或安装预构建的Python扩展模块(C )(即未通过Setuptools编译的库)
- 致命错误LNK1112:通过 vcvarsall .bat x86 运行构建'X86'模块计算机类型'x64'与目标计算机类型冲突
- Python 3.4.3 未能构建这些模块: _hashlib _ssl.
- Android系统模块不会在构建上产生输出
- 在Swift Project中使用OpENCV时,无法构建模块“ OpenCV2”
- 创建一个生成文件来构建切换的模块
- 使用自定义模块构建 python 解释器时出现问题
- 构建 tesseract3 训练模块时出现意外错误:"not declared in this scope"
- 未解析的外部符号构建 Python 模块
- boost是否需要特殊的python模块才能在linux中构建
- 如何配置Qt来构建所有模块
- 使用额外的模块构建opencv