ObjC внутренности. Почему моя попытка утки не удалась?

Я пытался использовать id создать утку, набрав в цель-c. Концепция выглядит хорошо в теории, но провалилась на практике. Я не смог использовать какие-либо параметры в моих методах. Методы были вызваны, но параметры были неправильными. Я получал BAD_ACESS для объектов и случайные значения для примитивов. Я приложил простой пример ниже.

Вопрос: кто-нибудь знает, почему параметры методов неверны? Что происходит под капотом объектива-с?

Примечание: меня интересуют детали. Я знаю, как сделать приведенный ниже пример.

Пример: я создал простой класс Test который передается другому классу с помощью свойства id test,

@implementation Test
- (void) aSampleMethodWithFloat:(float) f andInt: (int) i {
    NSLog(@"Parameters: %f, %i\n", f, i);
}
@end

Затем в классе выполняется следующий цикл:

for (int i=0; i < 10; ++i) {
    float f=i*0.1f;
    [tst aSampleMethodWithFloat:f andInt:i]; // warning no method found.
}

Вот вывод, который я получаю. Как видите, метод был вызван, но параметры были неверными.

Parameters: 0.000000, 0
Parameters: -0.000000, 1069128089
Parameters: -0.000000, 1070176665
Parameters: 2.000000, 1070805811
Parameters: -0.000000, 1071225241
Parameters: 0.000000, 1071644672
Parameters: 2.000000, 1071854387
Parameters: 36893488147419103232.000000, 1072064102
Parameters: -0.000000, 1072273817
Parameters: -36893488147419103232.000000, 1072483532

Обновить:

Я случайно обнаружил, что когда я добавляю декларацию aSampleMethodWith... в класс с for В цикле предупреждение исчезает, и метод класса Test вызывается правильно.

Обновление 2: Как указывает JeremyP, непосредственная причина проблемы заключается в том, что числа с плавающей точкой рассматриваются как двойные. Но кто-нибудь знает почему? (следуя принципу 5).

Согласно @eman, вызов переводится в простой вызов функции C и директиву компилятора, чтобы получить SEL, Таким образом, @selector запутывается. Но почему? Компилятор имеет всю необходимую информацию о типе в первом вызове метода. Кто-нибудь знает хороший источник информации о внутренностях Objective C, я искал Язык программирования Objective C, но я не нашел ответ.

5 ответов

Решение

По умолчанию значения с плавающей запятой передаются как двойные, а не как числа с плавающей запятой. Компилятор не знает, в точке, где [tst aSampleMethodWithFloat:f andInt:i]; Случается, что он должен только передать число с плавающей запятой, поэтому он увеличивает f в два раза. Это означает, что в методе, когда компилятор знает, что он имеет дело с плавающей точкой, f - это число с плавающей точкой, образованное первыми четырьмя байтами двойного числа, переданного методу, а i - целое число, образованное из вторых четырех байтов дважды прошло.

Вы можете исправить это либо

  • изменив первый параметр aSampleMethodWithFloat: andInt: на двойной
  • импортировать объявление интерфейса Test в файл, где вы его используете.

Обратите внимание, что при использовании чисел с плавающей запятой нет никакого выигрыша, за исключением небольшого количества места. Вы можете также использовать везде двойные числа.

Я думаю, что JeremyP прав насчет проблемы, связанной с удвоением против поплавков. Что касается деталей реализации, то при отправке сообщений в Objective-C используется objc_msgSend(id theReceiver, SEL theSelector, ..) Функция С (для некоторых глубоких подробностей см. Здесь). Вы можете смоделировать те же результаты отправки метода следующим образом:

SEL theSelector = @selector(aSampleMethodWithFloat:andInt:);
objc_msgSend(self.test, theSelector, 1.5f, 5);

SEL это просто число, которое соответствует функции (которая определяется динамически на основе сигнатуры метода). objc_msgSend затем ищет фактический указатель на функцию (типа IMP) метода и вызывает его. поскольку objc_msgSend имеет переменное количество аргументов, он просто будет использовать столько, сколько вы передадите. Если вы должны были сделать:

objc_msgSend(self.test, theSelector, 1.5f);

Было бы правильно использовать 1.5f и иметь мусор для другой переменной. Поскольку сигнатура метода обычно обозначает количество аргументов, это трудно сделать при обычном использовании.

Без подписи, доступной в вызывающей точке, неизвестно, какой тип должны иметь параметры. Предполагается, что неопределенные методы принимают ... в качестве параметров, а это не то, что делает ваш. Если в этот момент компилятор видит какой-либо интерфейс, в котором существует рассматриваемый метод, будет использовано это определение.

Вы можете убрать предупреждение, создав такую ​​категорию:

@interface NSObject (MyTestCategory)
- (void) aSampleMethodWithFloat:(float) f andInt: (int) i;
@end

Проблема здесь заключается в разделительной линии между C и Objective-C. Тип id определяет любой объект, но int и float не являются объектами. Компилятор должен знать тип C всех аргументов и тип возврата любого метода, который вы вызываете. Без объявления предполагается, что метод возвращает идентификатор и принимает произвольное количество аргументов идентификатора. Но id несовместим с int и float, поэтому значение не передается правильно. Вот почему он работает правильно, когда вы предоставляете объявление - тогда он знает, что ваш int - это int, а ваш float - это float.

Другие вопросы по тегам