В каких условиях могут быть экземплярыRespondToSelector: вернуть true, но выполнитьSelector: выставить исключение

У меня есть код, распространяемый в библиотеке, который выглядит так:

if ([[NSString class] instancesRespondToSelector: @selector(JSONValue)]) {
  NSString *jsonString = [[[NSString alloc] initWithData: jsonData encoding: NSUTF8StringEncoding] autorelease];
  dict = [jsonString performSelector: @selector(JSONValue)];
}

По какой-то причине возникает исключение -[__NSCFString JSONValue]: unrecognized selector sent to instance при вызове метода performSelector:. Это код, который распространяется в библиотеке, которую я написал, но я не могу воспроизвести или отладить ее сам. Вместо этого сторонняя сторона сообщает об этой проблеме. При каких условиях может instancesRespondToSelector: при вызове метода с помощью performSelector: выполнить исключение?

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

@implementation NSString (OurHappyCategory)

+ (BOOL)instancesRespondToSelector:(SEL)aSelector
{
  return YES;
}

@end

Это объясняет, почему код выполняется, но, конечно, это будет очень плохо. Есть ли способ, которым может возникнуть эта проблема, которая имеет смысл?

Ответы

Ответ 1

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

NSString *jsonString = [[[NSString alloc] initWithData: jsonData encoding: NSUTF8StringEncoding] autorelease];
if ([jsonString respondsToSelector: @selector(JSONValue)]) {
  dict = [jsonString performSelector: @selector(JSONValue)];
}

но ваша проблема, вероятно, связана с компиляцией или связыванием расширения... если категория добавлена ​​в библиотеку, тогда вам нужно будет побрызгать флагом -ObjC.

EDIT:
Я немного поработал над воспроизведением этой проблемы... которой я не могу... есть ли у вас дополнительная информация.. например, это сбой, возникающий только на симуляторе или только на устройстве, iOS 4.x, GNU linker vs LLDB linker, различия ABI/Runtime?

Ответ 2

Я предполагаю, что вы не импортировали стороннюю библиотеку правильно. Обычно эти методы добавляются как категория в NSString, мне случилось, что я мог видеть файл .h, но .m не был скомпилирован. Вы можете проверить его внутри цели xcode → фазы сборки → скомпилировать источники. Или проверьте, есть ли у вас этот флаг внутри Project → Build Settings → Other linker flag = -all_load

Ответ 3

Исключение не происходит непосредственно из среды выполнения Objective-C в ответ на отправку сообщения, которое экземпляр не распознает. Он исходит из -[NSObject doesNotRecognizeSelector:], который вызывается в конце различных способов поиска и механизмов переадресации.

Однако все, что угодно, может вызвать -doesNotRecognizeSelector:. Класс может "дезавуировать" унаследованный метод, переопределяя его и делая переопределение просто вызывать -doesNotRecognizeSelector:. Поскольку документировано, это приведет к тому, что вы видите, несмотря на то, что -respondsToSelector:+instancesRespondToSelector:) возвращает YES.

Я не мог сказать вам, почему __NSCFString делает это в вашем случае конечного пользователя. Приложение, использующее вашу библиотеку с помощью категорий или методов swizzling, чтобы изменить методы этого класса?

Кроме того, показывает ли ваш журнал фактический захват трассировки стека в исключении? Это может быть информативным.

Ответ 4

Существует нечетная возможность: может быть, что performSelector сам каким-то образом выполняется в другой среде с загрузкой ссылок, чем остальная часть кода. Не совсем (или даже приблизительно) уверен, как это может произойти.

Ответ 5

Удостоверьтесь, что никто не выполняет функцию