Подтвердить что ты не робот

Можно ли использовать isKindOfClass: против экземпляра NSString для определения типа?

Из isKindOfClass: документация метода в NSObject:

Будьте внимательны при использовании этого метода для объектов, представленных кластером классов. Из-за природы кластеров классов возвращаемый объект не всегда может быть типом, который вы ожидали.

В документации далее приводится пример того, почему вы никогда не должны спрашивать что-то вроде следующего экземпляра NSArray:

// DO NOT DO THIS!
if ([myArray isKindOfClass:[NSMutableArray class]])
{
    // Modify the object
}

Теперь, чтобы привести пример другого использования, скажем, у меня есть экземпляр NSObject, где я хотел бы определить, есть ли у меня NSString или NSArray.

Оба эти типа являются кластерными классами, но, как видно из вышеприведенной документации, опасность заключается в ответе на isKindOfClass: слишком утвердительно (ответив ДА иногда, когда у вас действительно нет изменяемого массива), но задавая вопрос о простое членство в кластере все равно будет действительным.

Пример:

NSObject *originalValue;

// originalValue gets set to some instance

if ( [originalValue isKindOfClass:[NSString class]] )
   // Do something with string

Правильно ли это предположение? Действительно ли безопасно использовать isKindOfClass: против экземпляров кластера классов для определения членства? Меня особенно интересует ответ на вездесущие NSString, NSArray и NSDictionary, но мне было бы интересно узнать, может ли он быть обобщенным.

4b9b3361

Ответ 1

Предупреждение в документации использует пример NSMutableArray. Предположим, что какой-то разработчик использует NSMutableArray как базовый класс для собственной реализации нового типа массива CoolFunctioningArray. Это CoolFunctioningArray по дизайну не изменяет. Однако isKindOfClass вернет YES в isKindOfClass:[NSMutableArray class], что верно, но по дизайну это не так.

isKindOfClass: вернет YES, если получатель где-то наследует класс, переданный как аргумент. isMemberOfClass: возвращает YES только в том случае, если получатель является экземпляром класса, переданного только как аргумент, то есть не включает подклассы.

В целом isKindOfClass: небезопасно использовать для проверки членства. Если экземпляр возвращает YES для isKindOfClass:[NSString class], вы знаете только, что он будет отвечать на все методы, определенные в классе NSString, но вы точно не знаете, что может сделать реализация этих методов. Кто-то может подклассифицировать NSString, чтобы вызвать исключение для метода длины.

Я думаю, вы могли бы использовать isKindOfClass: для такого тестирования, особенно если вы работаете со своим собственным кодом, который вы (как хороший самаритянин), созданный таким образом, будет реагировать так, как мы все ожидаем (например, метод длины подкласса NSString, чтобы вернуть длину строки, которую он представляет, вместо воссоздания исключения). Если вы используете множество внешних библиотек странных разработчиков (таких как разработчик CoolFunctioningArray, которые следует расстрелять), вы должны использовать метод isKindOfClass с осторожностью и предпочтительно использовать метод isMemberOfClass: (возможно, несколько раз, чтобы проверить принадлежность к группе классов).

Ответ 2

Давайте рассмотрим следующий код:

if ([dict isKindOfClass:[NSMutableDictionary class]])
{
    [(NSMutableDictionary*)dict setObject:@1 forKey:@"1"];
}

Я думаю, что истинная цель заметки Apple, приводимая в вопросе, заключается в том, что этот код может легко привести к сбою. И проблема здесь заключается в бесплатном перекрестке с CoreFoundation. Рассмотрим более подробно 4 варианта:

NSDictionary* dict = [NSDictionary new];
NSMutableDictionary* dict = [NSMutableDictionary new];
CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

Действительно, во ВСЕХ 4 вариантах истинный класс dict будет __NSCFDictionary. И все 4 варианта пройдут тест в первом фрагменте кода. Но в двух случаях (объявления NSDictionary и CFDictionaryRef) мы сбой записываем что-то вроде этого:

* Завершение приложения из-за неперехваченного исключения "NSInternalInconsistencyException", причина: '- [__ NSCFDictionary setObject: forKey:]: метод мутирования, отправленный в неизменяемый объект

Итак, все становится немного яснее. В 2 случаях нам разрешается изменять объект, а в 2 случаях нет. Это зависит от функции создания, и, вероятно, состояние мутирования отслеживается самим объектом __NSCFDictionary.

Но почему мы используем один и тот же класс для изменяемых и неизменяемых объектов? Возможный ответ - из-за ограничений языка C (и CoreFoundation - это API C, как мы знаем). Какая проблема у нас в C? Декларации изменяемых и неизменяемых типов словарей должны отражать следующее: CFMutableDictionaryRef type - это подтип CFDictionaryRef. Но у C нет механизмов для этого. Но мы хотим иметь возможность передавать объект CFMutableDictionary в функцию, ожидающую объект CFDictionary, и, желательно, без компилятора, излучающего раздражающие предупреждения. Что мы должны делать?

Давайте посмотрим на следующие объявления типа CoreFoundation:

typedef const struct __CFDictionary * CFDictionaryRef;
typedef struct __CFDictionary * CFMutableDictionaryRef;

Как мы видим здесь, CFDictionary и CFMutableDictionary представлены одним и тем же типом и отличаются только модификатором const. Итак, здесь начинаются неприятности.

То же самое относится и к NSArray, но ситуация немного сложнее. Когда вы создаете NSArray или NSMutableArray напрямую, вы получите некоторые специальные классы (__NSArrayI и __NSArrayM соответственно). Для этого класса авария не воспроизводится. Но когда вы создаете CFArray или CFMutableArray, все остается неизменным. Будьте осторожны!

Ответ 3

Вы неправильно читаете предупреждение. Все, что он говорит, состоит в том, что только потому, что оно внутренне представлено как нечто изменяемое, не означает, что вы должны пытаться его мутировать, потому что мутация может нарушить контракт между вами и тем, из кого вы получили массив; это может нарушить их предположения о том, что вы не будете мутировать его.

Однако тест, заданный isKindOfClass:, определенно остается в силе. Если [something isKindOfClass:[NSMutableArray class]], то это экземпляр NSMutableArray или его подкласс, что в значительной степени означает его изменение.

Ответ 4

Это странное предупреждение в документах Apple. Это как сказать: "будьте осторожны с этой функцией, потому что, если вы используете ее с другим сломанным кодом, это не сработает". Но вы можете сказать это с чем угодно.

Если дизайн следует принципу подстановки, который он должен, то isKindOfClass будет работать. (Википедия: Принцип замены Лискова)

Если какой-то код нарушает принцип, возвращая экземпляр подкласса NSMutableArray, который не отвечает на addObject: и другие методы NSMutableArray, тогда у этого кода есть проблема... если только он не является небольшим временным взломом...