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