views:

102

answers:

2

This is a problem which has been bugging me for a while now. I'm still pretty new with some of these patterns so you'll have to forgive me (and correct me) if I use any of the terms incorrectly.

My Methodology

I've created a game engine. All of the objects in my game engine use inversion of control to get dependencies. These dependencies all implement protocols and are never accessed directly in the project, other than during the bootstrapping phase. In order to get these objects, I have the concept of a service locator. The service locator's job is to locate an object which conforms to a specific protocol and return it. It's a lot like a factory, but it should handle the dependencies as well.

In order to provide the services to the service locator, I have what I call service specifiers. The service locator knows about all of the service specifiers in the project, and when an object is requested, attempts to get an instance of an object conforming to the provided protocol from each of them. This object is then returned to the caller. What's cool about this set up is the service specifier also knows about a service locator, so if it has any dependencies, it just asks the service locator for those specific dependencies.

To give an example, I have an object called HighScoreManager. HighScoreManager implements the PHighScoreManager protocol. At any time if an instance of PHighScoreManager is required, it can be retrieved by calling:

id<PHighScoreManager> highScoreManager = [ServiceLocator resolve: @protocol(PHighScoreManager)];

Thus, inversion of control. However, most of the time it isn't even necessary to do this, because most classes are located in a service specifier, if one required PHighScoreManager as a dependency, then it is retrieved through the service locator. Thus, I have a nice flat approach to inversion of control.

My Problem

Because I want the code from my game engine to be shared, I have it compiled as a static library. This works awesome for everything else, but seems to get a little tricky with the service locator. The problem is some services change on a game to game basis. In my above example, a score in one game might be a time and in another it might be points. Thus, HighScoreManager depends on an instance of PHighScoreCreator, which tells it how to create a PScore objecct.

In order to provide PHighScoreCreator to HighScoreManager, I need to have a service specifier for my game. The only way I could think of to accomplish this was to use the Cocoa version of reflections. After digging around, I found out classes were discoverable through NSBundle, but it seems there's no way to get the current bundle. Thus, if I want to be able to search out my service specifiers, I would have to compile my game logic into its own bundle, and then have the engine search out this bundle and load it. In order to do this I'd have to create a third project to house both the engine code and the game logic bundle, when in reality I'd like to just have a game project which used the engine static library.

My Real Question

So after all of that, my question is

  1. Is there a better way to do what I'm trying to accomplish in Cocoa Touch, or
  2. Is there a way to discover classes which conform to my service specifier protocol from the main bundle?

Thanks for the help and taking the time to read the question.

-helixed

+1  A: 

Have a look at:

  • +[NSBundle mainBundle];
  • +[NSBundle bundleForClass:];
  • +[NSBundle bundleWithIdentifier:];
  • +[NSBundle allBundles];
  • +[NSBundle allFrameworks];

These allow you to interact programmatically with the various bundles at runtime. Once you have a bundle to work with there are a number of strategies you could employ to find the specific class(es) you are looking for. For example:

  1. Retrieve the bundle identifier — this will be an NSString like @"com.example.GameEngineClient".
  2. Transform it into a legal Objective-C class name by stripping everything before the last dot, or replacing all the dots with underscores, or whatever, and then appending a predefined protocol name. Your protocol from above, for instance, might result in a string like @"GameEngineClient_PHighScoreManager".
  3. Get the bundle's designated class for your protocol using NSClassFromString().

Now you can create an instance of the class provided by the bundle author, that implements whatever protocol you have specified.

The Objective-C runtime is a beautiful thing!

Kaelin Colclasure
I tried going this path and the problem is you can't know ahead of time what class names will be included in the bundle. Therefore, you can't iterate through all of the classes in the bundle.
helixed
I've edited the answer to provide some suggestions on what to do next after you have an NSBundle instance. Hope this helps. :-)
Kaelin Colclasure
I have a feeling this might not be allowed in an iPhone app, but I might be wrong. I'm pretty sure apple doesn't want you loading classes at runtime on the iPhone. But great question.
Ron Srebro
Ron: This pattern is not about *loading* classes at runtime, it's about *locating* them. The code above assumes all of these classes are in frameworks already linked into your application.Yes, this pattern is also useful with dynamically loaded code, but I think what helixed is looking for is to reduce the amount of boilerplate initialization code needed to use his game engine.
Kaelin Colclasure
There is nothing wrong with creating classes dynamically at runtime. You can't load new code into your app, that is a violation of the dev agreement. That is why several bundle methods for loading code are missing in the iOS SDK.
logancautrell
Sorry for such a late reply to this question. Is there any way you could give a code example with your post? I've looked quick a bit at these classes, and I haven't been able to find a way to combine them to do what I want them to do. Thanks.
helixed
A: 

Sounds like you need to use the functions of the Objective-C runtime. First you can get a list of all available classes via objc_getClassList. Then you can iterate over all the classes and check if they conform to your protocol with class_conformsToProtocol. You shouldn’t use +conformsToProtocol: messages here, since there are classes in the runtime that don’t support this selector.

Sven