Есть ли чистый способ использовать указатели (идентификаторы) в качестве ключей в NSMutableDictionary?

#objective-c #ios #nsmutabledictionary

#objective-c #iOS #nsmutabledictionary

Вопрос:

Я использую NSMutableDictionary для хранения того, что фактически является дескриптором для определенных классов, потому что я бы предпочел не тратить память на добавление дескриптора к каждому экземпляру классов, поскольку только очень небольшое подмножество из 1000 объектов будет иметь дескриптор.

К сожалению, учитывая:

 MyClass* p = [MyClass thingy];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSString* description = @"blah"; //does this work? If not I'm just simplifying for this example.
[dict setObject:description forKey:p]; // BZZZZT a copy of p is made using NSCopying

MyClass* found = [dict objectForKey:p]; //returns nil, as p becomes a different copy.
  

Так что это не работает.

Я могу взломать его, передав NSNumber следующим образом:

 [dict setObject:description forKey:[NSNumber numberWithInt:(int)p]]; // this is cool
  

Но это не только некрасиво, но и подвержено ошибкам, поскольку это нестандартно.

Имея это в виду, есть ли чистый способ сделать это?

Комментарии:

1. Вместо этого используйте NSMapTable. Смотрите: nshipster.com/nshashtable-and-nsmaptable

Ответ №1:

NSValue подтверждает NSCopying согласно ссылке на класс NSValue. Итак, вы можете использовать экземпляр NSValue в качестве ключа.

Используйте valueWithPointer: для преобразования значения указателя вверх.

 NSString* foo = @"foo";
id bar = @"bar";

NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
[dict setObject:@"forKey:foo" forKey:foo];
[dict setObject:@"forKey:bar" forKey:bar];
[dict setObject:@"forKey:[NSValue... foo]" forKey:[NSValue valueWithPointer:foo]];
[dict setObject:@"forKey:[NSValue... bar]" forKey:[NSValue valueWithPointer:bar]];

NSLog(@"%@", [dict objectForKey:foo]); 
NSLog(@"%@", [dict objectForKey:bar]); 
NSLog(@"%@", [dict objectForKey:[NSValue valueWithPointer:foo]]); 
NSLog(@"%@", [dict objectForKey:[NSValue valueWithPointer:bar]]); 
  

Дает

 2013-01-24 04:42:14.051 a.out[67640:707] forKey:foo
2013-01-24 04:42:14.052 a.out[67640:707] forKey:bar
2013-01-24 04:42:14.053 a.out[67640:707] forKey:[NSValue... foo]
2013-01-24 04:42:14.053 a.out[67640:707] forKey:[NSValue... bar]
  

Комментарии:

1. В iOS 6 с ARC я должен сделать это, чтобы использовать NSValue: [NSValue valueWithPointer:(__bridge const void *)(obj)] . Это не позволит мне использовать объект без его приведения.

2. Как насчет: @((NSInteger)obj)?

Ответ №2:

Ваша проблема в том, что NSDictionary копирует свои ключи. Либо заставьте свой класс реализовать NSCopying, либо используйте CFMutableDictionary с обратным вызовом ключа, который не копирует (он бесплатный, соединенный мостом с NSMutableDictionary, поэтому вы можете использовать его точно так же после его создания).

Ответ №3:

Возможно, вам будет проще использовать objc_setAssociatedObject / objc_getAssociatedObject . Они описаны здесь.

Комментарии:

1. Я бы предпочел придерживаться ограничений того, как класс будет нормально функционировать, или просто не использовать его вообще. Я не хочу, чтобы мой API использовал NSDictionary, который должен использоваться совершенно нестандартным образом для функционирования.

2. Вы отвечаете на неправильный ответ? Мое предложение вообще не касается NSDictionary.

3. Упс, извините, я просмотрел в первый раз и неправильно понял. Это, хотя и является улучшением, мне не помогает, потому что я хочу иметь возможность хранить объект на основе контекста, поскольку несколько фрагментов кода потенциально могут ссылаться на один и тот же объект, и мне не нужна 100%-ная ассоциация, поскольку я хочу, чтобы другой код мог видеть, связан ли объект, а не находился в режиме ожидания. Я не уверен, что хорошо это объяснил.

Ответ №4:

Мое личное решение:

переопределите - (BOOL) isEqual:(id)object в вашем пользовательском классе.

в этом методе сравните каждое свойство в self и object . если они совпадают, верните YES

Я не уверен, как вы реализовали свой - (id) copyWithZone:(NSZone *)zone метод, но для моего тестового проекта у меня он отлично работает.

вложение:

 - (id) copyWithZone:(NSZone *)zone{
    testObject *copy = [[testObject allocWithZone:zone] init];
    NSString *tempCopyString = [NSString stringWithString:self.string]
    copy.string = tempCopyString;
    return copy;
}

- (BOOL) isEqual:(id)object{
    testObject *newObject = (testObject*)object;
    if ([newObject.string isEqualToString:object.string]) {
        return YES;
    }
    else {
        return NO;
    }
}
  

Комментарии:

1. Мой класс не легкий, я вообще не хочу создавать копии — это, вероятно, обойдется дороже, чем простое добавление указателя к каждому классу, который мог бы иметь «описание».

Ответ №5:

Поскольку это проблема, которая может возникать часто, я создал следующий класс для связывания объектов с другими объектами без копирования ключа. Идентификатор определяется по адресу ключевого объекта. Это подкласс NSMutableDictionary, поэтому доступны все методы этого класса.

ObjectMap.h

 //
//  ObjectMap.h
//
//  Created by René Dekker on 07/03/2012.
//  Copyright (c) 2012 Renevision.
//

#import <Foundation/Foundation.h>

@interface ObjectMap : NSMutableDictionary

  (ObjectMap *) objectMap;

- (bool) containsObject:(id)aKey;

// use the following instead of setObject:forKey: in IOS 6, to avoid warnings about NSCopying
- (void) setObject:(id)anObject forObjectKey:(id)aKey;


@end
  

ObjectMap.m

 //
//  ObjectMap.m
//
//  Created by René Dekker on 07/03/2012.
//  Copyright (c) 2012 Renevision.
//

#import "ObjectMap.h"
#import <CoreFoundation/CoreFoundation.h>

@interface ObjectMapEnumerator : NSEnumerator 

@end

@implementation ObjectMapEnumerator {
    NSUInteger size;
    NSUInteger currentIndex;
    id *keysArray;
}

- (NSArray *) allObjects
{
    return [NSArray arrayWithObjects:keysArray count:size];
}

- (id) nextObject
{
    if (currentIndex >= size) {
        return nil;
    }
    return keysArray[currentIndex  ];
}

- (id) initWithDict:(CFDictionaryRef)dict
{
    if (!(self = [super init])) {
        return nil;
    }
    size = CFDictionaryGetCount(dict);
    keysArray = malloc( size * sizeof(id) );
    currentIndex = 0;
    CFDictionaryGetKeysAndValues(dict, (const void **)keysArray, NULL);
    return self;
}

- (void) dealloc
{
    free(keysArray);
    [super dealloc];
}

@end

@implementation ObjectMap {
    CFMutableDictionaryRef theDictionary;
}

- (void) setObject:(id)anObject forKey:(id)aKey
{
    CFDictionarySetValue(theDictionary, aKey, anObject);
}

- (void) setObject:(id)anObject forObjectKey:(id)aKey
{
    CFDictionarySetValue(theDictionary, aKey, anObject);
}

- (id) objectForKey:(id)aKey
{
    return CFDictionaryGetValue(theDictionary, aKey);
}

- (void) removeObjectForKey:(id)aKey
{
    CFDictionaryRemoveValue(theDictionary, aKey);
}

- (void) removeAllObjects
{
    CFDictionaryRemoveAllValues(theDictionary);
}

- (bool) containsObject:(id)aKey
{
    return CFDictionaryContainsKey(theDictionary, aKey);
}

- (NSUInteger) count
{
    return CFDictionaryGetCount(theDictionary);
}

- (NSEnumerator *)keyEnumerator
{
    return [[[ObjectMapEnumerator alloc] initWithDict:theDictionary] autorelease];
}

#pragma - Object Life Cycle

static void dictionaryRelease(CFAllocatorRef allocator, const void* value) {
    if (0 != value) {
        CFRelease(value);
    }
}

static const void *dictionaryRetain(CFAllocatorRef allocator, const void* value) {
    if (0 != value) {
        return CFRetain(value);
    }
    return 0;
}

static CFDictionaryKeyCallBacks callbacks = { 0, amp;dictionaryRetain, amp;dictionaryRelease, amp;CFCopyDescription, NULL, NULL };

- (id) init
{
    if (!(self = [super init])) {
        return nil;
    }
    theDictionary = CFDictionaryCreateMutable(NULL, 0, amp;callbacks, amp;kCFTypeDictionaryValueCallBacks);
    return self;
}

- (void) dealloc
{
    CFRelease(theDictionary);
    [super dealloc];
}

  (ObjectMap *) objectMap
{
    return [[[ObjectMap alloc] init] autorelease];
}

@end