Класс Objective-C, возвращаемый alloc, путают с неверным классом при инициализации - PullRequest
5 голосов
/ 23 апреля 2011

Мне показалось, что я понял базовый Objective-C в том, что касается методов alloc и init ..., но, похоже, я не понимаю. Я свел проблему, с которой столкнулся, к минимальному примеру ниже. (Для примера я помещаю все исходные тексты в один файл, но проблема возникает точно так же, если исходный код разбивается на несколько исходных файлов и заголовочных файлов, как обычно).

Вот краткий обзор того, что делает код и что происходит, когда я его запускаю.

Я определяю два класса MyInteger и MyFloat, которые почти идентичны, за исключением того, что один имеет дело с типом int, а другой с float. Оба имеют методы инициализатора, называемые initWithValue: однако с другим типом аргумента. Существует #define для контроля того, определен класс MyInteger или нет, причина в том, что он ведет к другому поведению программы, даже если этот класс никогда не используется.

Функция main () использует только MyFloat. Первые две строки делают это: выделяет экземпляр MyFloat и инициализирует его значением 50.0. Затем он печатает значение. В зависимости от того, определен MyInteger или нет, я получаю два разных выхода.

Без определения MyInteger, как я и ожидал:


[4138:903] float parameter value:50.000000
[4138:903] Value:50.000000
(next two output lines omitted)

С определением MyInteger, к моему большому удивлению:


[4192:903] float parameter value:0.000000
[4192:903] Value:0.000000
(next two output lines omitted)

Мне кажется, что компилятор обрабатывает вызов initWithValue: как будто он принадлежит классу MyInteger. Следующие две строки main () проверяют это путем приведения [MyFloat alloc] к типу MyFloat *. И это генерирует выходные данные, как ожидалось, даже когда MyInteger определен:


[4296:903] float parameter value:0.000000
[4296:903] Value:0.000000
[4296:903] float parameter value:50.000000
[4296:903] Value with cast:50.000000

Пожалуйста, объясните, что происходит! Я боролся с этим уже более 24 часов, вплоть до открытия дверей, чтобы немного нагреться, чтобы мой компьютер мог остыть :-) Спасибо!

Еще одна странность в том, что если я переместу определение MyInteger ниже определения MyFloat, то все будет «хорошо» - работает, как я и ожидал. История слишком часто доказывает, что я ошибаюсь, и я подозреваю, что виноват компилятор. В любом случае, вот информация о компиляторе и проекте: Использование Xcode 4.0.2. Пробовал со всеми 3 вариантами компилятора (GCC 4.2, LLVM GCC 4.2 и LLVM Compiler 2.0). Проект Xcode для этих примеров был настроен с использованием стандартной конфигурации для инструмента командной строки Mac OS X на основе Foundation.


#define DO_DEFINE_MYINTEGER 1

//------------- define MyInteger --------------
#if DO_DEFINE_MYINTEGER
@interface MyInteger : NSObject {
    int _value;
}
-(id)initWithValue:(int)value;
@end

@implementation MyInteger
-(id)initWithValue:(int)value {
    self= [super init];
    if (self) {
        _value= value;
    }
    return self;
}
@end
#endif


//------------- define MyFloat --------------
@interface MyFloat : NSObject {
    float _value;
}
-(id)initWithValue:(float)value;
-(float)theValue;
@end

@implementation MyFloat
-(id)initWithValue:(float)value {
    self= [super init];
    if (self) {
        NSLog(@"float parameter value:%f",value);
        _value= value;
    }
    return self;
}
-(float)theValue {
    return _value;
}
@end

//--------------- main ------------------------
int main (int argc, const char * argv[])
{
    MyFloat *mf1= [[MyFloat alloc] initWithValue:50.0f];
    NSLog(@"Value:%f",[mf1 theValue]);

    MyFloat *mf2= [((MyFloat*)[MyFloat alloc]) initWithValue:50.0f];
    NSLog(@"Value with cast:%f",[mf2 theValue]);

    return 0;
}

Ответы [ 2 ]

5 голосов
/ 23 апреля 2011

+alloc прототипируется как возвращающий id, и когда компилятор сталкивается с выбором нескольких методов -initWithValue:, он генерирует код для вызова первого найденного метода.Когда MyInteger определен, это означает, что компилятор сгенерирует код для преобразования 50.0 и передаст его как целочисленный аргумент.Обратите внимание, что целочисленные и плавающие аргументы передаются по-разному: первый в стеке, а второй в регистре с плавающей запятой.

Во время выполнения, поскольку отправка сообщения обрабатывается динамически, вызывается правильный метод - но этоМетод предполагает, что аргумент value был передан в регистр с плавающей запятой.Но это не то место, куда его помещает вызывающий код, поэтому вызываемый метод получает неверные результаты, когда читает этот регистр.

Приведение типов работает, потому что оно явно сообщает компилятору, какой из методов будет вызываться во время выполнения,который позволяет ему генерировать правильный код вызова, передавая value в регистр с плавающей запятой вместо стека.

Edit : все это сказанное, ответ NSResponder делает некоторыеочень хорошие моменты тоже.В Objective-C очень плохая идея объявлять методы, которые имеют одно и то же имя, но имеют разные подписи (например, аргументы и возвращаемые типы), а метод с именем -initWithValue: подразумевает, что его аргумент является объектом NSValue.

4 голосов
/ 23 апреля 2011

Это плохая идея иметь две сигнатуры метода, в которых имена методов идентичны, но типы не идентичны, особенно если они компилируются в одном и том же файле. Компилятор запутывается здесь из-за того, должно ли выражение сообщения передавать int или float.

Если вы посмотрите на NSNumber, вы увидите, что существуют отдельные методы -initWithFloat:, -initWithDouble: и -initWithInt:, например.

Кроме того, если у вас есть метод со словом «значение» в названии, большинство разработчиков Cocoa считают, что он ожидает параметр NSValue.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...