Objective-C build system that can execute modules

I am working on a small project of my hobby, where I have a large structure that takes care of all the main tasks. This core will not be by itself, it needs dozens of subsystems that actually do the hard work. Currently, I have written only one subsystem, so itโ€™s still easy to make a difference.

I have many code places where the kernel interacts with subsystems, and I do not want to change the kernel every time I add a new subsystem. My idea was to make it modular.

For a long time I saw something similar in the game engine, where you could create a new console command using a preprocessor macro. That was all you had to do - after compilation it worked instantly in the game.

So, take the game engine as an example for my case. I posted the comments in the code below to make my question more obvious.

My question is: how can I implement a modular system in Objective-C, built at compile time, and does not require changing anything other than the modules themselves?

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 add a new command with something like:

 REGISTER_COMMAND(MyClassName, "theCommand") 

Remember that the code above is not my specific case. In addition, I do not want external modules, they need to be compiled as if they were implemented initially. Objective-C is excellent, just like C ++ or C.

Update
Clarification: I know how to do this with the plist file, but if I selected this, I would just save them in my actual code. I am looking for a C / C ++ / Objective-C solution that allows me to simply add a module with a preprocessor macro.

Update 2
Adding rewards - I would really like some good ideas for this.

+4
source share
7 answers

Well, if this should be a 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
source

I do not quite understand the problem. However, from what I can assemble, the anchor point is to find the hook at runtime so that you can register your modules.

One such hook is the class method +(void)load . load is called for each loaded class and category. For static link classes / categories, this will be when you launch your application. Even if you don't want to use Objective-C, you can still create a class just for the hook, which provides its load method.

+5
source

This is kind of like whet @verec. You can add a special class called ModuleList to your project. Each module wishing to register can do this by adding a category to the ModuleList . You can put this in a macro. Using the objc / runtime functions, you can iterate over added properties or methods. (i.e. all properties / methods that do not arise in NSObject )

The advantage is that you do not need to iterate over all classes.

+3
source

I am doing something like this in a new project that I am working on. I store information about modules (classes) in an XML file (plist will work well too), including class features, its name, etc. At runtime, I load an XML file and when a particular class is needed, I create it based on its name. For ease of use / good encapsulation, I have a BaseModule class from which "module classes" are inherited. BaseModule has an initWithModuleName: method that accepts the name of the module (as specified in the XML file):

 - (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 is much more to this system than I mentioned here, and it is based on my code, but not copied or pasted from it. First, I use Objective-C's ability to search for a method name at runtime and dynamically dispatch to call module methods declared / defined in subclasses of BaseModule, but not in BaseModule itself. These methods are described in the XML file.

But the end result is that all I need to do to add a new module is its definition in the "ModuleDefinitions.xml" file in my project and add implementation classes to it. The rest of the program will automatically take its presence and begin to use it.

+2
source

I am adding another answer in order to describe in detail what was said in the chat above, as this can be useful for everyone who has a similar problem.

This decision is supposed to be based on the following:

  • all modules and the kernel are part of the same executable file and are compiled and linked to each other, as in any standard project, and
  • There is a naming convention between commands and module class names.

With this in mind, the following code (in the "kernel") returns a list of all classes in the executable file, whose moms correspond to the specified prefix:

 #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 get all the classes whose name begins with "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 the commands and module class names are the same, then all that is.

In other words, to add a new module, just add its sources to the project, and you're done. Runtime in the kernel will simply raise it if the name of the "main class" of the module matches any naming convention you have established between the "commands" and the names of the module classes.

those. not necessary

 REGISTER_COMMAND(MyClassName, "theCommand") 

in any modular code, and no macros are required.

+2
source

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

You are looking for some means to populate it in such a way that when you are asked to enter a string (your command), it returns another string to be the name of the class class objective-c that you can instantiate.

How do you want your dictionary to be completed?

If at compile time, either on your own, in some kind of file, or any tool you write, you will have to write a little source code that inserts

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

If thatโ€™s all you need, then you donโ€™t even need a macro, but some registry class whose only task is to populate your dictionary with any new pair of class / command with which you add.

Something simplified:

 @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 clarify how this example does not exactly fit your question?

+1
source

aaaand .. here is another solution built on load / initialize, no Macro - if you subclass the module, your command handler will be raised at boot time. Perhaps you could make it work based on your class that implements some protocol if you want ...

 #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
source

Source: https://habr.com/ru/post/1388677/


All Articles