1

I'm working on creating a Cocoa library for connecting to the embedded devices my company makes. I have a superclass, which we'll call Device, and a number of subclasses for the different device models, which we'll call Device1, Device2, etc.

I need to support the automatic detection of the device model, which can be determined after connecting and logging into the device. Since the login code is common to all devices, it can be handled in the superclass. After logging in, the device will need to be represented by the appropriate subclass for its model.

I envisaged instantiating an object of the Device superclass, logging into the device to read the model and then replacing the object by an instance of the appropriate subclass, say Device1. I know that it's possible to return a different object in an -init method but my problem is that the comms to the device can be lengthy so should probably be implemented with callbacks/delegates.

Is it possible to change the subclass of the instantiated object after the -init method? Or is there a simpler/better way to achieve what I'm trying to do?

Richard Pickett
  • 482
  • 8
  • 17

2 Answers2

1

One approach would be to use an instance of a custom subclass of NSProxy rather than of your Device superclass to handle the initial login and device detection. You could design your NSProxy to turn itself into an instance of the appropriate Device subclass once the device type is known.

NSProxy takes advantage of a feature of the Objective-C runtime system that it allows its instances to notice when they've received messages intended for an instance of the real target. The proxy can then either forward the message to its target, or turn itself into an instance of the target type and forward the message to itself. (Sounds, weird, I know, but very cool in practice.)

Here's the first paragraph of the class description:

NSProxy is an abstract superclass defining an API for objects that act as stand-ins for other objects or for objects that don’t exist yet. Typically, a message to a proxy is forwarded to the real object or causes the proxy to load (or transform itself into) the real object. Subclasses of NSProxy can be used to implement transparent distributed messaging (for example, NSDistantObject) or for lazy instantiation of objects that are expensive to create.

Your NSProxy implementation could include the methods needed for connecting, logging in, etc., but not the methods implemented by the Device class hierarchy. The proxy instance could select the target class when it detects the device type. It would then automagically morph itself into an instance of that class as soon as you send it a message implemented by a Device, but not by the proxy.

jlehr
  • 15,557
  • 5
  • 43
  • 45
  • So would I make my parent `Device` class inherit from `NSProxy` instead of `NSObject`? Are you able to provide a basic code sample on using `NSProxy` in this way? – Richard Pickett Feb 27 '11 at 22:57
  • No, your subclass of `NSProxy` would create instances that essentially *pretend* that they're instances of a subclass of `Device`. There's an excellent article and example code solution by Mike Ash here: http://www.mikeash.com/pyblog/friday-qa-2010-02-26-futures.html – jlehr Feb 28 '11 at 05:27
  • I've looked a bit further into `NSProxy` and it looks like it will work suitably for me. Thanks for your help. Now I know what to look for. – Richard Pickett Mar 04 '11 at 12:28
0

I would suggest you create a factory of some sort to instantiate your concrete Device subclasses, this removes the hassels of doing dirty tricks like changing a objects's class and is probably a cleaner solution, here is a rough example how that could look like:

@implementation DeviceFactory

+ (Device *) detectAndMakeDevice
{
  // Do your detection code here, this assumes that Device1 and Device2 are
  // both subclasses of Device.

  Device * dev;

  if(deviceConnected == kDevice1)
    dev = [[Device1 alloc] init];

  else if(deviceConnected == kDevice2)
    dev = [[Device2 alloc] init];

  else
    dev = nil;

  return dev;
}

@end
Torsten
  • 834
  • 9
  • 13
  • I was thinking along these lines originally. However, the detection code can take up to a minute (on slow comms), which is a long time to block for. Blocking means that the UI wouldn't get feedback and the user cannot cancel. – Richard Pickett Feb 27 '11 at 22:53
  • Ah, I misunderstood your question then. If the waiting for the device is the problem, it might help if you initialize the device in another Thread. Have a look at [NSOperation](http://developer.apple.com/cocoa/managingconcurrency.html) which abstracts multi-threading quite nicely. You could then have a property like `BOOL isInitialized` in your Device class and send message `- (void) initializationFinishedForDevice:(Device *)` to a `DeviceDelegate` object to notify your other components. [More infos on how to use delegates](http://stackoverflow.com/questions/626898/#626946) – Torsten Feb 28 '11 at 15:28
  • And when the initialisation is finished, how do I change the object from a `Device` to a `Device1`? – Richard Pickett Mar 01 '11 at 23:39