Заседнал съм в разбирането на динамичното обвързване в Objective-c

Току-що започнах да изучавам Objective-C, чета Програмиране в Objective-C 3-то издание от Стивън Г. Кочан.

Има параграф, обясняващ механизма на полиморфизма:

По време на изпълнение системата за изпълнение на Objective-C ще провери действителния клас на обекта, съхранен вътре в dataValue1(идентификационен обект) и ще избере подходящия метод от правилния клас за изпълнение. Въпреки това, в по-общ случай, компилаторът може да генерира неправилен код, за да предаде аргументи на метод или да обработи неговата върната стойност. Това би се случило, ако един метод вземе обект като аргумент, а другият взе стойност с плаваща запетая, например. Или ако единият метод е върнал обект, а другият е върнал цяло число, например. Ако несъответствието между два метода е просто различен тип обект (например методът add: на Fraction приема обект Fraction като свой аргумент и връща един, а методът add: на Complex взема и връща 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. Указателите могат да се считат за цели числа и при x86_64 ABI те се предават на целевия метод по същия начин.

Ако обаче сте имали друг клас, напр.:

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

получаване на аргумент с плаваща запетая (както е споменато от Kochan), след това компилаторът, когато анализира:

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
Просто се уверете, че сте изрично прехвърлили променливи, преди да изпратите съобщения с двусмислени типове. Имайте предвид, че поради конвенциите за именуване на Cocoa, повечето аргументи са описани (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