【问题标题】:Cascading delegates and "Code That Doesn't Do What It Says"级联代表和“不按照它说的做的代码”
【发布时间】:2010-12-09 04:00:23
【问题描述】:

我一直在围绕 Apple 的授权和协议文档寻找答案,但一天多之后,我决定放弃,让你们试一试。我有三个类:HTTPManager、LoginManager 和 FetchManager。您可能会猜到这些类的作用,但要明确...

  • HTTPManager - 包装 NSURLConnection 并为 LoginManager 和 FetchManager 提供一个简单的接口来执行带有身份验证的 HTTP 请求。
  • LoginManager / FetchManager - 基本上是同一个类,但它们对 HTTPManager 消息的响应不同。

HTTPManager 需要一个委托来实现 HTTPManagerDelegate 协议,而 LoginManager 和 FetchManager 都这样做。 Login- 和 FetchManager 类还为我的应用程序委托提供了一个协议,以便数据可以一路返回用户界面。

在我的应用程序委托的init: 方法中,我初始化了一个登录和一个提取管理器,并收到以下警告:

warning: class 'MyAppDelegate' does not implement the 'HTTPManagerDelegate' protocol
warning: incompatible Objective-C types assigning 'struct HTTPManager *', expected 'struct LoginManager *'

被初始化的两个类都不是从 HTTPManager 派生的,但它们确实实现了 HTTPManagerDelegate 协议。产生上述警告的代码行是:

_loginMgr = [[LoginManager alloc] initWithDelegate:self];

那么到底是什么让 LoginManager 的initWithDelegate: 方法返回了HTTPManager*?没有继承,而且我的返回类型是正确的,所以对我来说这是一些我无法做到最好的黑暗形式的巫术。

这是我的应用程序的外壳。可能存在拼写错误和小不一致,所以在假设句法问题之前问我:

// HTTPManager.h

@protocol HTTPManagerDelegate
...
@end

@interface HTTPManager : NSObject
{
    id <HTTPManagerDelegate> _delegate;
    ...
}

- (HTTPManager *) initWithDelegate:(id <HTTPManagerDelegate>)delegate;
...

@end

// LoginManager.h

@protocol LoginManagerDelegate
...
@end

@interface LoginManager : NSObject <HTTPManagerDelegate>
{
    id <LoginManagerDelegate> _delegate;
    ...
}

- (LoginManager *) initWithDelegate:(id <LoginManagerDelegate>)delegate;
...

@end

// MyAppDelegate.h

@interface MyAppDelegate : NSObject <NSApplicationDelegate, LoginManagerDelegate, FetchManagerDelegate>
{
    LoginManager *_loginMgr;
    ...
}

...

@end

// MyAppDelegate.m

...

- (MyAppDelegate *) init
{
    self = [super init];

    if (self)
    {
        // WARNING HAPPENS HERE
        _loginMgr = [[LoginManager alloc] initWithDelegate:self];
        ...
    }

    return self;
}

...

提前致谢。

【问题讨论】:

    标签: objective-c cocoa xcode delegation


    【解决方案1】:

    问题是您有两个方法具有相同的方法签名-initWithDelegate:,但它们的参数和/或返回类型中的类型不同。编译器不能很好地处理这种情况,在某些情况下,它还可能导致运行时错误(不是你的情况,因为你的方法中的类型在大小上没有不同,它们都是指针)。

    原因(AFAIK)是运行时无法直接访问方法中使用的类型。它只是读取一个选择器(不包含类型信息)并根据这个选择器决定调用什么方法。为了帮助运行时将方法参数打包到堆栈上,编译器在编译时创建一个表,将选择器映射到参数和返回值类型。该表的每个选择器只有一个条目。因此,如果存在两个具有相同选择器但参数或返回值类型不同的方法,则该系统可能会失败。

    在你的情况下:

    -init... 方法应始终返回 id 而不是特定类型。

    这解决了不同返回类型的问题。另一个问题(不同的参数类型)更难解决。您可以在方法声明中省略协议规范 (initWithDelegate:(id)delegate),也可以为这两种方法指定不同的名称:

    - (id) initWithHttpMgrDelegate:(id <HTTPManagerDelegate>)delegate;
    - (id) initWithLoginMgrDelegate:(id <LoginManagerDelegate>)delegate;
    

    【讨论】:

    • 实际上,编译器可以从接收者的类型推断出正确的方法签名——如果接收者是静态类型的,即。这里的问题是alloc返回id,所以编译器没有信息可以处理。
    • 谢谢奥莱!这回答了我所有的问题等等。
    • 我不明白为什么 init... 应该返回 id。如果它返回一个更具体的类型,它只会在编译时导致更好的类型检查,而在运行时没有区别,对吧?
    猜你喜欢
    • 1970-01-01
    • 2010-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-28
    • 1970-01-01
    • 2016-03-31
    • 1970-01-01
    相关资源
    最近更新 更多