При каких условиях instancesRespondToSelector: возвращает true, но PerformSelector: генерирует исключение

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

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

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

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

@implementation NSString (OurHappyCategory)

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

@end

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


person ThomasW    schedule 09.04.2013    source источник
comment
Вы уверены, что ваш метод JSONValue в результате возвращает словарь? Может быть, это ваша проблема.   -  person Maggie    schedule 09.04.2013
comment
@Maggie Неважно, что возвращает метод, исключение вызывается вызовом метода. Тип возвращаемого значения для метода JSONValueid.   -  person ThomasW    schedule 09.04.2013
comment
Вы уверены, что третья сторона использует этот код с пунктом if?   -  person Marcelo Fabri    schedule 09.04.2013
comment
@MarceloFabri Этот код находится в моей статической библиотеке, которую они включают в свое приложение. Я знаю, что там возникает исключение, потому что мой код перехватывает исключение и пишет определенное сообщение.   -  person ThomasW    schedule 09.04.2013
comment
вы уверены, что это единственный возможный вызов JSONValue? Может быть, третья сторона не связывает вашу библиотеку и сама не вызывает JSONValue?   -  person Jonathan Cichon    schedule 09.04.2013
comment
@JonathanCichon Мой код перехватывает исключение и записывает определенное сообщение, поэтому я знаю, что это происходит в моем коде.   -  person ThomasW    schedule 09.04.2013
comment
Могла ли третья сторона переопределить JSONValue: так, чтобы она выдавала исключение, когда вы передаете (тип) данных, которые использует ваш пример?   -  person Fred    schedule 09.04.2013
comment
@Fred, параметр JSONValue не передается, поэтому я не думаю, что проблема в этом. Возможно, они изменили поведение класса NSString, но я не понимаю, почему они это сделали.   -  person ThomasW    schedule 09.04.2013
comment
Мне не удалось придумать код, подтверждающий это, но есть ли шанс, что это связано с конфликтующими именами/аргументами категорий? Если ваш код распространяется в библиотеке, вы все равно должны префиксить его, если у них есть собственная категория JSONValue, что не редкость.   -  person MaxGabriel    schedule 18.04.2013
comment
@MaxGabriel, но если бы у них была собственная реализация JSONValue, которую реализует NSString, почему она выдавала бы исключение?   -  person ThomasW    schedule 18.04.2013
comment
Эх, забудьте эту теорию. Это кажется многообещающим: developer.apple.com/library/mac/# qa/qa2006/qa1490.html Также этот вопрос SO: stackoverflow.com/questions/10416779/ Я думаю, что это придает большое значение теории Андреа. Может быть, попросить их дважды проверить свои флаги компилятора?   -  person MaxGabriel    schedule 18.04.2013
comment
Я только что попробовал тест, и если вы не используете флаг -ObjC, instancesRespondToSelector: вернет NO. Если вы используете флаг -ObjC, тогда instancesRespondToSelector: вернет YES, и исключение не будет выдано. -all_load имеет тот же эффект, что и -ObjC.   -  person ThomasW    schedule 18.04.2013
comment
Попросите третью сторону создать минимальный тестовый пример, демонстрирующий эту проблему.   -  person tc.    schedule 24.04.2013
comment
@тс. Я попросил их о тестовом примере, но я еще не получил от них известий.   -  person ThomasW    schedule 24.04.2013
comment
@ThomasW, у тебя есть еще информация? например, сбой происходит только на ios 4.x или какие-либо различия ABI/runtime? симулятор против устройства?   -  person Grady Player    schedule 24.04.2013
comment
Судя по всему, третья сторона нашла решение или обходной путь для этой проблемы, но пока не предоставила мне никаких подробностей.   -  person ThomasW    schedule 25.04.2013


Ответы (5)


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

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

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

РЕДАКТИРОВАТЬ:
Я немного поработал над воспроизведением этой проблемы... что я не могу... у вас есть дополнительная информация... например, сбой происходит только на симулятор или только на устройстве, iOS 4.x, компоновщик GNU и компоновщик LLDB, различия ABI/Runtime?

person Grady Player    schedule 23.04.2013
comment
Я согласен, что это может быть связано с кластером, но я не могу придумать, как это может быть проблемой. У вас есть конкретный пример того, как вы могли бы получить описанный результат? Я предполагаю, что OP пытается вообще избежать создания строки, если она не может быть JSONValue'd. (например, у него есть запасной вариант, который включает прямой анализ данных) - person Jesse Rusak; 24.04.2013
comment
хорошо, если категория находится в NSString, а NSCFString на самом деле является суперклассом NSString - person Grady Player; 24.04.2013
comment
__NSCFString является подклассом NSString (на самом деле NSMutableString), поэтому я до сих пор не понимаю, как это может вызвать проблему. - person Jesse Rusak; 24.04.2013
comment
Я предполагаю, что это взаимодействие между статическими библиотеками и категориями и PerformSelector. Когда PerformSelector ищет NSString, кажется возможным найти версию, привязанную к статической библиотеке, к которой не привязана категория. Похоже на то, что один и тот же класс загружается двумя загрузчиками классов в Java. Прямой вызов работает, потому что указатель экземпляра всегда связан с правильной версией класса. - person Hot Licks; 24.04.2013
comment
@HotLicks тип ABI/Runtime не работает таким образом ... если у нас есть скомпилированная версия, мы могли бы посмотреть объектный файл с nm и посмотреть, есть ли вообще символ, и находится ли он в разделе undefined . - person Grady Player; 24.04.2013
comment
Судя по всему, так оно и работает. Просто никто не определил конкретную причуду. - person Hot Licks; 26.04.2013

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

person Andrea    schedule 11.04.2013
comment
-all_load больше не нужен, достаточно -ObjC. Также это не объясняет, почему instancesRespondToSelector возвращает true. - person Jonathan Cichon; 11.04.2013
comment
В данном случае мы говорим о стороннем приложении, которое использует мою библиотеку, поэтому у меня нет доступа к настройкам сборки. - person ThomasW; 11.04.2013
comment
Спросите их, могут ли они проверить или дать вам весь проект, у меня почти такая же проблема с интеграцией MKNetworkKit в новый проект. Я делал много раз, но вчера это просто не работало, я мог отправлять сообщения, но не реализовывал категории той библиотеки. Добавление флага -all-load все прошло нормально. Для полноты я не отправлял сообщения responseToSelector, но автодополнение и компоновщик строились нормально. - person Andrea; 11.04.2013
comment
Мне было интересно, может быть, .m для категории не было включено, но тогда я не вижу, как instancesRespondToSelector вернет YES. И, предположительно, вызов instances.. и вызов JSONValue находятся в одной и той же единице компиляции и будут привязаны к одним и тем же определениям. - person Hot Licks; 18.04.2013
comment
Так что не будет ли -all_load больше не нужным зависеть от того, какую версию Xcode они используют? Я слышал о некоторых людях, использующих сумасшедшие старые версии Xcode. - person MaxGabriel; 18.04.2013

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

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

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

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

person Ken Thomases    schedule 24.04.2013

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

person Hot Licks    schedule 18.04.2013
comment
Не могли бы вы уточнить, что вы подразумеваете под загруженной ссылкой средой? - person ThomasW; 18.04.2013
comment
@ThomasW - результаты привязки, создания исполняемого модуля, как бы вы это ни называли. Терминология отличается для каждой платформы, и я не могу за ними угнаться. В частности, если некоторые части находятся в отдельном библиотечном модуле, возможно, что они не могут видеть все содержимое в другом модуле или наоборот. - person Hot Licks; 18.04.2013
comment
В любом случае, если это проблема, вероятно, будет работать, чтобы привести jsonString к фиктивному типу интерфейса, который определяет JSONValue, и выполнить вызов напрямую (или так же напрямую, как Objective-C делает любой вызов). - person Hot Licks; 18.04.2013
comment
Я не понимаю, как кастинг jsonString может изменить список методов, на которые он отвечает. - person Jesse Rusak; 24.04.2013
comment
@JesseRusak - полагаю, ты многого не видишь. Приведение типов не изменит того, на что отвечает строка, но позволит компилятору проглотить ее, избегая необходимости использовать PerformSelector (что, вероятно, является подозреваемым в этой загадке). - person Hot Licks; 24.04.2013
comment
@HotLicks Не нужно быть грубым; Я просто не видел, к чему ты клонишь. Извиняюсь! - person Jesse Rusak; 24.04.2013
comment
Итак, дизассемблирование performSelector: в основном: - (id)performSelector:(SEL)selector { if (selector) { return [self selector]; } else { return [self doesNotRecognizeSelector:NULL]; } (если вы извините за злоупотребление синтаксисом.) Таким образом, я не думаю, что это источник проблемы, поскольку либо: селектор не равен нулю, и это похоже на отправка сообщения, или селектор равен нулю, и в этом случае ошибка не укажет имя селектора. - person Jesse Rusak; 24.04.2013
comment
@JesseRusak - Как скажешь. У тебя есть теория получше? - person Hot Licks; 24.04.2013
comment
@JesseRusak - (Имейте в виду, что мы говорим о CATEGORY в NSString. И нескольких статических библиотеках.) - person Hot Licks; 24.04.2013
comment
Я полностью согласен с тем, что PerformSelector: здесь подозрительно (я бы просто привел к id и позвонил напрямую, как вы предложили), но я не понимаю, как это может вызвать вышеуказанные симптомы. (Посмотрите на разборку!) Честно говоря, я думаю, что наиболее вероятным ответом является тот, который предложен в редакции ОП! Вот что я вам скажу: я проголосую за ваш ответ, и вы перестанете злиться на меня за попытку помочь. - person Jesse Rusak; 24.04.2013

Убедитесь, что никто не использует функцию

person Stephen J    schedule 23.04.2013