1

Recently (reviewing some code) I stumbled upon an oddity that results in a bug in our program.

An API we are using has the following implementation (that I am going to write in Swift, even though the original code is in Objective-C)

internal class MyUUID: NSUUID { }

Which is completely useless as it always returns an empty instance.

I am going to paste the code from my playground here for explanation purposes.

For example: creating a simple NSUUID would be something like this:

let a = NSUUID()
a.description //this creates a valid uuid

While creating a MyUUID should be similar

let b = MyUUID()
b.description //it returns an instance, but is completely empty.

But it doesn't work.

Inspecting a little bit more, reveals the NSUUID initialiser creates a __NSConcreteUUID instance, while MyUUID doesn't and it doesn't matter what I try to do, it won't create an appropriate UUID.

So, my question: Is it possible to be able to create a child implementation of NSUUID?

halfer
  • 19,824
  • 17
  • 99
  • 186
rodrigoelp
  • 2,550
  • 1
  • 19
  • 29

2 Answers2

3

Your evidence would appear empirically to answer your own question: it's not possible. NSUUID would appear to be a class cluster rather than a single class, which effectively prevents subclassing.

An alternative idea to Aaron's:

Implement an object that has an NSUUID rather than that is one. Implement -forwardingTargetForSelector: and return your instance of NSUUID. Consider overriding -isKindOfClass:, but ideally don't unless you have to. Then you should be able to pass your class as though it were an NSUUID to anyone that expects one without their knowing the difference.

Given that the solution depends upon the fallback mechanism built into dynamic messaging, I suspect there's no Swift equivalent; however if you define your class as Objective-C then it should be equally usable from Swift.

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • I think I like your answer more, but I've updated my answer with some more details. I think I provided an okay (albeit dangerous and deprecated) way to subclass a class cluster - would like a second pair of eyes on my solution though. – Aaron Brager Oct 28 '14 at 17:45
  • If I may re-awaken this thread: I wanted to do something similar, where for convenience, I wanted a UUID's `description` to be just the UUIDString, not the `<__NSConcreteUUID >` format. Even in Swift 2.2, is this still my only option? I don't want to have Obj-C code just for the sake of this convenience. – BaseZen Apr 19 '16 at 17:35
2

You could use class_setSuperclass to change the superclass of MyUUID at runtime. This approach would be illegal in Swift, due to type safety, but you could still do it in Objective-C.

Depending on your actual goals you may be able to use CFUUIDRef instead.


As requested, here's an example of the class_setSuperclass approach. Just drop this in to a new single view project.

#import <objc/runtime.h>

@interface MyUUID : NSUUID

- (void) UUIDWithHello;

@end

@implementation MyUUID

- (void) UUIDWithHello {
    NSLog(@"Hello! %@", self.UUIDString);
}

@end

@interface ViewController ()


@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // Make a UUID that you want to subclass
    NSUUID *uuid = [[NSUUID alloc] init];
    NSLog(@"Initial UUID: %@", uuid.UUIDString);

    // Ignore deprecation warnings, since class_setSuperclass is deprecated
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"

    // Change MyUUID to inherit from the NSUUID's hidden subclass instead of NSUUID
    class_setSuperclass([MyUUID class], [uuid class]);  // [uuid class] is __NSConcreteUUID

    // Turn deprecation warnings back on
#pragma GCC diagnostic pop

    // Make a new myUUID and print it
    MyUUID *myUuid = [[MyUUID alloc] init];
    [myUuid UUIDWithHello];
}

@end    

Note that this is a bit dangerous. If whatever secret subclass NSUUID has additional instance variables, it will require more memory, which [MyUUID alloc] won't request. This could cause a crash later when something requests these instance variables.

To get around this, you could instead instantiate your MyUUID instance like this:

NSLog(@"Initial UUID's class: %@", NSStringFromClass(uuid.class));
Class topSecretUUIDSubclass = uuid.class; // __NSConcreteUUID
MyUUID *myUuid2 = [[topSecretUUIDSubclass alloc] init];
[myUuid2 UUIDWithHello];
object_setClass(myUuid2, [MyUUID class]);

Basically this will make myUuid2 a __NSConcreteUUID and then change it to a MyUUID. However, this will only work if MyUUID doesn't add any instance variables.

If MyUUID does need to add its own instance variables, it will need to override +alloc to provide additional memory for these instance variables, using class_createInstance().

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
  • Can you give me a sample of this? – rodrigoelp Oct 28 '14 at 03:18
  • The 'objc_setSuperclass' approach :) – rodrigoelp Oct 28 '14 at 04:50
  • Wouldn't it be easier to just change the superclass dynamically in +initialize? Also, why would the first approach be dangerous? You're changing the class's notion of its superclass, so [MyUUID alloc] should then call [__NSConcreteWhatever alloc], no? – dgatwood Aug 27 '18 at 21:14
  • Never mind. I know what you're saying. The underlying struct would collide with the underlying struct for the new superclass. Yes, that makes this a bad idea. :-/ – dgatwood Aug 27 '18 at 21:24