ObjC внутренности. Почему моя попытка утки не удалась? - PullRequest
2 голосов
/ 25 апреля 2010

Я пытался использовать id для создания утки, набирающей текст в target-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 ]

2 голосов
/ 25 апреля 2010

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

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

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

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

1 голос
/ 25 апреля 2010

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

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 и имел бы мусор для другой переменной,Поскольку сигнатура метода обычно обозначает количество аргументов, это трудно сделать при обычном использовании.

0 голосов
/ 25 апреля 2010

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

0 голосов
/ 25 апреля 2010

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

0 голосов
/ 25 апреля 2010

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

@interface NSObject (MyTestCategory)
- (void) aSampleMethodWithFloat:(float) f andInt: (int) i;
@end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...