【问题标题】:Objective-C Parent Child Pointers and their relationshipObjective-C 父子指针及其关系
【发布时间】:2012-01-21 18:12:34
【问题描述】:

我有一个父类

@interface Parent : NSObject
@end

还有一个子类

@interface Child : Parent
@end

一般来说,我们可以像这样将子对象存储在父指针中

Parent *p = [Child alloc]init];

当我像这样将父对象存储在子指针中时,我很惊讶

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

虽然编译器发出警告“语义错误不兼容的指针”,但当我运行它时,它就像第一种情况一样工作。我无法理解为什么运行时允许它工作?

【问题讨论】:

    标签: objective-c


    【解决方案1】:

    你没有说为什么你对这种行为感到惊讶,但一种可能是 staticdynamic 方法查找和键入之间的区别。

    首先请注意,使用 inheritance,无论您有一个 Parent 引用,您实际上都可能有一个 Child 引用 - 后者可以做前者可以做的所有事情,并且可以替代一个。 p>

    现在在许多面向对象的语言中,例如 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
       ...
    }
    

    以上内容有意不讨论 dynamicstatic 的相对优缺点,您最好从一本好书阅读。

    【讨论】:

      【解决方案2】:

      之所以有效,是因为 init 方法被声明为返回 id,而不是 Parent*Child*id 类型可以隐式转换为任何对象指针类型。

      您会收到警告,因为编译器知道alloc/init 序列通常会返回指向接收alloc 消息的类型的指针。

      【讨论】:

        【解决方案3】:

        原因很简单。 Child 对象保证实现 Parent 实现的所有方法,因为 Child 是 Parent 的子类。因此,无需担心在 Child 类型的对象上调用特定的 Parent 方法会导致异常。但是,Parent 对象不一定实现 Child 实现的所有方法。所以后来的方法调用(消息发送到)c 可能会导致异常。编译器不知道 c 不是 Child 对象,因为这是它声明的类型。本质上,这是编译器曾经有机会警告您所做的事情可能是错误的地方。

        运行时完全是另一回事。在运行时,对象指针是相同的。因此,没有理由不能在源代码中声明为 Child* 类型的指针中存储(对父对象的引用)。由于上述原因,您通常不应该这样做。只要你只调用Parent定义的方法,就不会出问题,但是如果你尝试调用一个Child特有的方法,你会得到一个运行时异常。

        简而言之,让编译器通过尽可能严格地键入变量来帮助您捕获错误。如果您只打算使用 Parent 定义的功能,则可以将 Child 对象存储在类型为变量的 Parent 中。如果你真的需要一个通用/无类型的对象变量,你可以使用id。在这种情况下,如果您不确定所讨论的对象是否实现了给定的方法,您可以用 if ([object resondsToSelector:@selector(theMethodName:)]) 括起方法调用。

        【讨论】:

          【解决方案4】:

          我假设运行时允许它工作,就像您可以将 NSMutableArray 分配给 NSArray 一样。您正在将超类 Parent 的实例分配给 Child,这是不必要的,因为 Child 已经继承了 parent。

          【讨论】:

          • 虽然运行时不会仅仅抱怨将父对象分配给子指针,但是如果您调用定义在子类而不是父类中的方法,它会崩溃。因此,即使指针是子对象,但实际对象是没有方法的父对象。编译器将毫无问题地编译,因为编译器指针是 Child 并且 Child 具有该方法。除了为什么运行时允许事件指针分配之外,这一切都是有道理的,但这又是 Objective-c 的活力出现了!
          【解决方案5】:

          它之所以“有效”是因为 Objective-C 实际处理传递给对象的消息的方式。从技术上讲,您可以将所有指针视为 NSObject 并且您的程序将以完全相同的方式运行,因为最终像 [myObject someMessage] 这样的调用被编译为对 myObject 指向的对象的选择器查找(无论它可能是什么! ),然后调用该选择器(如果它由真实对象处理)的实现。 objc.h (IIRC) 中有一组 C 函数可用于自己完成这项工作 - 编译器基本上将使用 [ ] 语法的消息传递转换为这些 C 函数调用。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-09-26
            • 2013-04-17
            • 1970-01-01
            • 1970-01-01
            • 2011-05-19
            • 1970-01-01
            • 1970-01-01
            • 2021-05-27
            相关资源
            最近更新 更多