Использование атрибута (copy) для копирования NSMutableDictionary вызывает сбой - PullRequest
0 голосов
/ 28 мая 2020

Следующая программа использует атрибут copy
для копирования NSMutableDictionary.
По всей видимости, копия в порядке, но, если я попытаюсь
добавить новый элемент в копию, программа выйдет из строя.
Это какой-то баг?
PS. Если это имеет значение, это НЕ AR C

#import <Foundation/Foundation.h>

@interface Dog: NSObject
@property (copy) NSMutableDictionary *dict;
@end

@implementation Dog
@synthesize dict;

- (id) init
{
    if ( (self = [super init]) ) {
    dict = [[NSMutableDictionary alloc] init];
    }
    return self;
}

- (void) print {
    for (id key in dict) {
    printf("%s --> %s\n", [key UTF8String], [dict[key] UTF8String] );
    }
}

@end

//------------------------------------------------------

int main() {
    Dog *dog1 = [[Dog alloc] init];
    Dog *dog2 = [[Dog alloc] init];

    dog1.dict[@"color"] = @"black";
    dog2.dict = dog1.dict;
    [dog2 print];
    // the print shows that dog2.dict is indeed a copy of dog1.dict
    // lldb shows that it is a shallow copy, which I guess is ok
    // since values are immutable.
    // ... so far so good.
    dog1.dict[@"tail"] = @"long";   // This goes smoothly
    // 
    // But...
    dog2.dict[@"tail"] = @"long";
    // here program crashes with the following message
    //
    // -[__NSDictionaryI setObject:forKeyedSubscript:]: unrecognized selector sent to instance 0x100108ee0
    //*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI setObject:forKeyedSubscript:]: unrecognized selector sent to instance 0x100108ee0'
    return(0);
}

РЕДАКТИРОВАТЬ: если я заменю строку

dog2.dict = dog1.dict;

на

[dog2.dict addEntriesFromDictionary: dog1.dict];

, тогда это сработает. sh.
Так что, хорошо, это правильный способ сделать это.
Но моя точка зрения: разве я не заслуживаю хотя бы предупреждения
от компилятора?

Ответы [ 2 ]

2 голосов
/ 28 мая 2020

Вы нарушили здесь некоторые правила, поэтому у вас все плохо. Как правило, вы не должны возвращать или принимать NSMutableDictionary в качестве собственности. Есть исключения (и вы должны кодировать это), но обычно этого не делать. Чтобы правильно закодировать это, вам необходимо отделить свой интерфейс от своей реализации.

Правильный интерфейс:

@interface Dog: NSObject
@property (copy) NSDictionary *dict;
@end

Этот объект обещает принять и вернуть NSDictionary, и он обещает, что принятый и возвращенные словари NSDictionaries будут независимыми копиями, поэтому вызывающей стороне не нужно беспокоиться о проблемах изменчивости. Затем нам нужно реализовать эти обещания:

@implementation Dog {
    NSMutableDictionary *mutableDict;
}

- (id) init
{
    if ( (self = [super init]) ) {
        mutableDict = [[NSMutableDictionary alloc] init];
    }
    return self;
}

- (void) print {
    for (id key in mutableDict) {
        printf("%s --> %s\n", [key UTF8String], [mutableDict[key] UTF8String] );
    }
}

- (NSDictionary *)dict {
    return [mutableDict copy]; // Add an -autorelease if this is MRC
}

- (void)setDict: (NSDictionary *)newValue {
    // I'm going to pretend this is ARC; for MRC, code this in your style.
    mutableDict = [newValue mutableCopy];
}

@end

С другой стороны, если вы действительно хотите поделиться состоянием с NSMutableDictionary, вы не должны копировать его, вы должны сохранить его, чтобы прояснить семантику.

@interface Dog: NSObject
@property (retain) NSMutableDictionary *dict;
@end

И это дает (надеюсь) понять вызывающему, что это разделяемое состояние и должно рассматриваться как таковое, и вызывающий должен делать копии, если они того пожелают. Но обычно этого следует избегать. И если вы сделаете go таким образом, я бы назвал свойство mutableDict или что-то в этом роде, чтобы прояснить, что это необычно. Для примера см. -[NSAttributedString mutableString].

На самом деле нет «изменяемой, но независимой копии» semanti c в аннотациях свойств Obj C, и я бы не стал ее создавать, если у вас нет очень сильная потребность (обычно связанная с производительностью). Обычно я бы просто возвращал неизменяемую копию и позволял вызывающей стороне создать свою собственную изменяемую копию.

Еще одно примечание: иногда Какао реализует «возвращает неизменяемую копию» как «просто возвращает изменяемую копию, преобразуя ее в неизменяемую. тип." Это повышает производительность, избегая копирования за счет того, что иногда данные меняются за вашей спиной. В качестве примера посмотрите документацию для -[NSView subviews]. На мой взгляд, вам следует избегать этого шаблона, если он не критичен для производительности (и даже в этом случае я бы сделал специальный метод, например -subviewsBackingStore или что-то подобное, чтобы прояснить, «это странно».

2 голосов
/ 28 мая 2020
Атрибут

copy создает экземпляр NSDictionary, а не NSMutableDictionary, поэтому он падает. Сначала измените свойство на retain с copy:

@property (retain) NSMutableDictionary* duct;

Затем вы можете сделать следующее:

Dog *dog2 = [[Dog alloc] init];
dog2.dict = [dog1.dict mutableCopy];
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...