Застрял в понимании динамического связывания в Objective-c

Я только начал изучать Objective-C, я читаю Программирование в Objective-C, 3-е издание Стивена Г. Кочана.

Есть параграф, объясняющий механизм полиморфизма:

Во время выполнения система выполнения Objective-C проверит фактический класс объекта, хранящегося внутри dataValue1 (объект id), и выберет соответствующий метод из правильного класса для выполнения. Однако в более общем случае компилятор может сгенерировать неправильный код для передачи аргументов методу или обработки его возвращаемого значения. Это могло бы произойти, если бы один метод принял объект в качестве аргумента, а другой принимает, например, значение с плавающей запятой. Или, например, если один метод вернул объект, а другой - целое число. Если несоответствие между двумя методами - это просто объект другого типа (например, метод add: Fraction принимает объект Fraction в качестве аргумента и возвращает его, а метод сложного add: принимает и возвращает объект Complex), компилятор будет по-прежнему генерирует правильный код, потому что адреса памяти (то есть указатели) в любом случае передаются как ссылки на объекты.

Я не совсем понимаю, что в первой части абзаца говорится, что компилятор может сгенерировать неправильный код, если я объявлю 2 метода в разных классах с одинаковым именем и разными типами аргументов. в то время как последняя часть абзаца говорит, что это нормально иметь 2 метода с одинаковым именем и разными аргументами и типами возвращаемых данных ... о нет ...

У меня есть следующий код, и они компилируются и работают нормально:

@implementation A
- (int) add:(int)a {
    return 1 + a;
}
@end
@implementation B
- (int) add: (B*) b {
    return 100;
}
@end
id a = [[A alloc] init];
id b = [[B alloc] init];
NSLog(@"A: %i, B %i", [a add:100], [b add:b]);

Изменить: как указано в тексте, который я цитировал, приведенный выше код должен вызывать ошибки, но он выдает только некоторые предупреждающие сообщения, Обнаружено несколько методов с именем "add:", Несовместимый указатель на отправку целочисленного преобразования "id" в параметр типа "int"

У меня есть опыт работы с Java и C ++, я знаю, что полиморфизм в Objective-C немного отличается от полиморфизма этих языков, но меня все еще смущает неопределенность (текст выделен жирным шрифтом).

Я думаю, что я что-то неправильно понял, не могли бы вы объяснить более подробно динамическое связывание в Objective-C для меня и тех, кому это нужно?

Спасибо!


person neevek    schedule 06.11.2011    source источник
comment
По теме: Зачем нужны неофициальные протоколы?.   -  person    schedule 06.11.2011


Ответы (2)


Вы не заметили ничего необычного, потому что эти два метода имеют одинаковую семантику вызова, например, для x86_64 ABI. Указатели можно рассматривать как целые числа, и в ABI x86_64 они передаются в целевой метод таким же образом.

Однако, если у вас был другой класс, например:

@implementation C
- (int)add:(float)number {
    return (int)number + 100;
}
@end

получает аргумент с плавающей запятой (как упомянул Кочан), а затем компилятор при синтаксическом анализе:

id a = [[A alloc] init];
id b = [[B alloc] init];
id c = [[C alloc] init];
NSLog(@"A: %i, B %i, C %i", [a add:100], [b add:b], [c add:100]);

не знал бы, что для [c add:100] он должен поместить 100 в регистр с плавающей запятой, как указано в x86_64 ABI. Следовательно, -[C add:], который ожидает, что аргумент с плавающей запятой находится в регистре с плавающей запятой, читает значение, не соответствующее аргументу 100.

Чтобы он работал, вам нужно либо объявить переменную со статическим типом:

C *c = [[C alloc] init];

или приведите его к правильному типу при отправке сообщения:

[(C *)c add:100];

В конце концов, отправка сообщения Objective-C - это вызов функции. Различные ABI могут иметь разную семантику для вызова функций с переменными аргументами, аргументами с плавающей запятой или целыми числами или возвращаемыми значениями или структурами вместо скалярных арифметических типов. Если компилятор видит разные сигнатуры методов, которые обрабатываются по-разному в соответствии с целевым ABI, и если доступной информации о типе недостаточно, он может в конечном итоге выбрать неправильную сигнатуру метода.

person Community    schedule 06.11.2011
comment
Спасибо за ваш вклад. теперь мое замешательство прояснилось. но это также заставляет меня чувствовать, что динамическое связывание в Objective-C не так безопасно. - person neevek; 06.11.2011
comment
Просто не забудьте явно привести любые переменные перед отправкой сообщений с неоднозначными типами. Обратите внимание, что из-за соглашений об именах в Какао описано большинство аргументов (stringByAppendingString:, numberWithFloat:), поэтому редко встречаются методы с одинаковым именем, но разных типов. - person andyvn22; 06.11.2011
comment
@Neevek Помимо того, что сказал andyvnn22, компилятор предупредит вас, если вы объявите методы с одинаковыми именами (селекторами) и разными подписями. - person ; 07.11.2011

Различие сделано потому, что в последнем случае разница только в классе аргументов. Complex* и Fraction* являются указателями, поэтому даже если есть путаница между двумя методами с одинаковыми именами, проблем нет.

С другой стороны, ситуация в вашем примере опасна, потому что один аргумент является указателем, а другой - int. Однако обезопасить себя от этого несложно:

NSLog(@"A: %i, B %i", [(A*)a add:100], [(B*)b add:b]);
person andyvn22    schedule 06.11.2011