5

我希望我的班级检测到一个新实例与某些现有实例等效(相对于 vis isEqual: 和hash),并且只创建唯一实例。这是我认为可以完成工作的代码,但我担心它会做一些我无法发现的愚蠢的事情......

假设它是这样的 NSURLRequest 子类:

// MyClass.h
@interface MyClass : NSMutableURLRequest
@end

// MyClass.m

@implementation MyClass

+ (NSMutableSet *)instances {

    static NSMutableSet *_instances;
    static dispatch_once_t once;

    dispatch_once(&once, ^{ _instances = [[NSMutableSet alloc] init];});
    return _instances;
}

- (id)initWithURL:(NSURL *)URL {

    self = [super initWithURL:URL];
    if (self) {
        if ([self.class.instances containsObject:self])
            self = [self.class.instances member:self];
        else
            [self.class.instances addObject:self];
    }
    return self;
}


// Caller.m
NSURL *urlA = [NSURL urlWithString:@"http://www.yahoo.com"];

MyClass *instance0 = [[MyClass alloc] initWithURL: urlA];
MyClass *instance1 = [[MyClass alloc] initWithURL: urlA];  // 2

BOOL works = instance0 == instance1;  // works => YES, but at what hidden cost?

问题:

  1. 在 init 中对 self 的第二个赋值看起来很奇怪,但并不疯狂。或者是吗?
  2. 认为第二个分配(实例1)被神奇地清理干净只是一厢情愿的编码吗?
4

2 回答 2

5
  1. 这并不疯狂,但在手动保留/释放模式下,您确实需要self事先释放,否则每次运行此方法时都会泄漏一个未初始化的对象。在 ARC 中,原始实例会自动为您释放。

  2. 见#1。

顺便说一句,对于任何通常只停留在一个答案的读者,下面的 bbum 答案包括一个线程安全实现的完整工作示例。强烈推荐给任何做这件事的人。

于 2013-04-05T17:33:24.867 回答
5

假设您真的想通过 URL 唯一,想一个更好的方法(行下方的原始答案)。如果不是,这也演示了同步原语的使用。

@interface UniqueByURLInstances:NSObject
@property(strong) NSURL *url;
@end

@implementation UniqueByURLInstances
static NSMutableDictionary *InstanceCache()
{
    static NSMutableDictionary *cache;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        cache = [NSMutableDictionary new];
    });
    return cache;
}

static dispatch_queue_t InstanceSerializationQueue()
{
    static dispatch_queue_t queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        queue = dispatch_queue_create("UniqueByURLInstances queue", DISPATCH_QUEUE_SERIAL);
    });
    return queue;
}

+ (instancetype)instanceWithURL:(NSURL*)URL
{
    __block UniqueByURLInstances *returnValue = nil;
    dispatch_sync(InstanceSerializationQueue(), ^{
        returnValue = [InstanceCache() objectForKey:URL];
        if (!returnValue)
        {
            returnValue = [[self alloc] initWithURL:URL];
        }
    });
    return returnValue;
}

- (id)initWithURL:(NSURL *)URL
{
    __block UniqueByURLInstances* returnValue = self;
    dispatch_sync(InstanceSerializationQueue(), ^{
        returnValue = [InstanceCache() objectForKey:URL];
        if (returnValue) return;

        returnValue = [super initWithURL:URL];
        if (returnValue) {
            [InstanceCache() setObject:returnValue forKey:URL];
        }

        _url = URL;
    });

    return returnValue;
}

- (void)dealloc {
    dispatch_sync(InstanceSerializationQueue(), ^{
        [InstanceCache() removeObjectForKey:_url];
    });
    // rest o' dealloc dance here 
}
@end

警告:上面输入了 SO - 从未运行过。我可能搞砸了。它假定 ARC 已启用。是的,当使用工厂方法时,它最终会查找 URL 两次,但是额外的查找应该会在分配和初始化的噪音中丢失。这样做意味着开发人员可以使用工厂或初始化程序,并且仍然可以看到唯一的实例,但是当该 URL 的实例已经存在时,在执行工厂方法时不会分配。

(如果你不能通过 URL 唯一,那么回到你的 NSMutableSet 并完全跳过工厂方法。)


查克说了什么,但还有一些额外的说明:

像这样重构你的代码:

+(NSMutableSet*)instances
{
    static NSMutableSet *_instances;
    dispatch_once( ...., ^{ _instances = [[NSMutableSet alloc] init];});
    return instances;
}

然后在您想要访问时调用该方法instances。它将所有代码本地化在一个地方并将其隔离+initialize(这并不是什么大问题)。

如果您的类可能是从多个线程中实例化的,那么您将希望使用同步原语围绕 check-allocate-or-return。我建议使用 dispatch_queue。

于 2013-04-05T18:00:20.033 回答