Search code examples
objective-cxcodeswiftxcode6nsuuid

Can NSUUID be extended by inheritance? How?


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?


Solution

  • 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().