1

最近(查看一些代码)我偶然发现了一个导致我们程序中的错误的奇怪现象。

我们正在使用的 API 具有以下实现(我将用 Swift 编写,即使原始代码在 Objective-C 中)

internal class MyUUID: NSUUID { }

这是完全没用的,因为它总是返回一个空实例。

我将从我的操场上粘贴代码以进行解释。

例如:创建一个简单的 NSUUID 是这样的:

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

虽然创建一个 MyUUID 应该是相似的

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

但它不起作用。

再检查一点,发现 NSUUID 初始化程序创建了一个__NSConcreteUUID实例,而 MyUUID 没有,我尝试做什么也没关系,它不会创建适当的 UUID。

所以,我的问题是:是否可以创建 NSUUID 的子实现?

4

2 回答 2

3

您的证据似乎可以凭经验回答您自己的问题:这是不可能的。NSUUID看起来是一个类簇而不是一个类,这有效地防止了子类化。

Aaron 的另一种想法:

实现一个有一个NSUUID而不是那个的对象。实现-forwardingTargetForSelector:并返回您的NSUUID. 考虑覆盖-isKindOfClass:,但最好不要,除非你必须这样做。然后,您应该能够通过您的课程,就好像它是NSUUID对任何期望您的课程但他们不知道差异的人一样。

鉴于该解决方案依赖于动态消息传递中内置的回退机制,我怀疑没有 Swift 等价物。但是,如果您将您的类定义为 Objective-C,那么它应该同样可以在 Swift 中使用。

于 2014-10-23T22:19:42.160 回答
2

您可以使用在运行时class_setSuperclass更改超类。MyUUID由于类型安全,这种方法在 Swift 中是非法的,但您仍然可以在 Objective-C 中这样做。

根据您的实际目标,您也许可以改用CFUUIDRef


根据要求,这是该方法的示例class_setSuperclass。只需将其放入一个新的单视图项目中即可。

#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    

请注意,这有点危险。如果任何秘密子类NSUUID有额外的实例变量,它将需要更多的内存,这[MyUUID alloc]不会请求。当某些东西请求这些实例变量时,这可能会导致稍后崩溃。

为了解决这个问题,您可以MyUUID像这样实例化您的实例:

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]);

基本上这将制作myUuid2a__NSConcreteUUID然后将其更改为 a MyUUID。但是,这仅在MyUUID不添加任何实例变量时才有效。

如果MyUUID 确实需要添加自己的实例变量,则需要重写+alloc以为这些实例变量提供额外的内存,使用class_createInstance().

于 2014-10-23T22:04:00.213 回答