Objective-C Родительские указатели на детей и их отношения - PullRequest
2 голосов
/ 21 января 2012

У меня есть родительский класс

@interface Parent : NSObject
@end

и детский класс

@interface Child : Parent
@end

В общем случае мы можем хранить дочерний объект в родительском указателе следующим образом

Parent *p = [Child alloc]init];

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

Child *c = [[Parent alloc] init];

Хотя компилятор выдает предупреждение «Несовместимые указатели семантической ошибки», но при запуске он работает как в первом случае. Я не могу понять, почему среда выполнения позволяет этому работать?

Ответы [ 5 ]

4 голосов
/ 21 января 2012

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

Первыйобратите внимание, что с наследованием везде, где у вас есть ссылка Parent, у вас действительно может быть Child один - последний может делать все, что может первый, и может заменить один.

Теперьво многих объектно-ориентированных языках, например C ++, типизирование переменных и поиск методов основаны на статических (то есть объявленных) типах.Так, например, в C ++ ваша переменная c считается экземпляром Child, а когда метод вызывается в c, компилятор C ++ определяет, какой метод вызывать, основываясь только на этом.Поэтому присвоение экземпляра Parent является небезопасным - у такого экземпляра нет методов Child, и обязательно произойдет сбой.Вы можете сделать присвоение безопасным в C ++, используя приведение:

Parent *p;
Child *c;
...

c = (Child *)p;

Приведение проверяет во время выполнения, является ли объект, на который ссылается p, Child (или любой класс, который наследуется от Childи т.д.) и выдаст ошибку, если нет.Поскольку это делается во время выполнения, оно обычно заключено в условное выражение, которое сначала проверяет, является ли p Child.

. Однако в Obj-C метод поиска выполняется динамически во время выполнения.основанный на фактическом типе ссылочного объекта, а не на типе переменной, которая на него ссылается.В результате этого, если ваша переменная c содержит ссылку на Parent и вы пытаетесь вызвать метод Child, вы получите ошибку времени выполнения (которая будет чистой, в случае C ++ ваш код будетвероятно, просто неисправность и / или взрыв).

Этот динамический характер Obj-C позволяет легко пропустить множество ошибок программирования, причем они становятся очевидными только при запуске кода - и так как это очень сложнополностью протестировать приложение после отправки кода клиентам.Поэтому компилятор Obj-C выполняет как можно больше проверок типов, чтобы помочь минимизировать количество ошибок, оставшихся во время выполнения.Однако, опять же из-за динамического характера, он иногда будет только предупреждать , а не сообщать о ошибке и отказываться от компиляции - как это было в вашем примере.

Решение в Obj-C такое же, как и в C ++ выше - используйте приведение, чтобы указать, что объект должен быть определенного типа и защитить его условным условием:

Parent *p;
Child *c;
...

if([p isKindOfClass:[Child class]) // we need a Child
{
   c = (Child *)p;
  ...
}
else
{  // handle p not being a Child
   ...
}

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

2 голосов
/ 21 января 2012

Причина этого довольно проста.Дочерние объекты гарантированно реализуют все методы, реализованные Parent, потому что Child является подклассом Parent.Таким образом, не беспокойтесь о том, что вызов определенного метода Parent для объекта типа Child вызовет исключение.Однако родительские объекты не обязательно реализуют все методы, реализованные в Child.Поэтому последующие вызовы методов (отправка сообщений) c может вызвать исключение.Компилятор не знает, что c не является дочерним объектом, поскольку это его объявленный тип.По сути, это единственное место, где у компилятора есть шанс предупредить вас, что то, что вы делаете, может быть неправильным.

Время выполнения - это совсем другое дело.Во время выполнения указатели объектов совпадают.Таким образом, нет никаких причин, по которым вы не можете хранить (ссылка на) объект Parent в указателе, объявленном в исходном коде как тип Child *.Вы обычно не должны этого делать по причинам, указанным выше.Пока вы вызываете только метод, определенный в Parent, это не вызовет проблем, но если вы попытаетесь вызвать метод, специфичный для Child, вы получите исключение времени выполнения.

Короче говоря, позвольтекомпилятор поможет вам отлавливать ошибки, печатая ваши переменные настолько строго, насколько это возможно.Можно хранить дочерний объект в переменной типа Parent, если вы собираетесь использовать только его функциональные возможности, определенные Parent.Если вам действительно нужна переменная общего / нетипизированного объекта, вы можете использовать id.В этом случае вы можете заключить вызов метода в if ([object resondsToSelector:@selector(theMethodName:)]), если вы не уверены, реализует ли данный объект данный метод.

2 голосов
/ 21 января 2012

Это работает, потому что объявлен метод init, возвращающий id, а не Parent* или Child*.Тип id может быть неявно преобразован в любой тип указателя объекта.

Вы получаете предупреждение, поскольку компилятор знает, что последовательность alloc / init обычно возвращает указатель на тип, который получил alloc сообщение.

1 голос
/ 21 января 2012

Это «работает» из-за способа, которым Objective-C фактически обрабатывает сообщения, передаваемые объектам.Технически, вы можете рассматривать все ваши указатели как NSObject, и ваша программа будет работать точно так же, потому что в конце вызов типа [myObject someMessage] компилируется до поиска селектора объекта, на который указывает myObject (каким бы он ни был!), а затем вызов реализации этого селектора (если он обрабатывается реальным объектом).В objc.h (IIRC) есть набор функций C, которые можно использовать для выполнения этой работы самостоятельно - компилятор в основном переводит передачу сообщений с использованием синтаксиса [] в эти вызовы функций C.

1 голос
/ 21 января 2012

Я бы предположил, что среда выполнения позволяет ему работать так же, как вы можете назначить NSMutableArray для NSArray.Вы выделяете экземпляр суперкласса Parent для Child, что было бы ненужным, поскольку Child уже наследует parent.

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