Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I implement the service locator pattern in Cocoa Touch across multiple projects?

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

like image 499
LandonSchropp Avatar asked Aug 06 '10 02:08

LandonSchropp


1 Answers

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!

like image 54
Kaelin Colclasure Avatar answered Nov 10 '22 10:11

Kaelin Colclasure