#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