Каскадные делегаты и «Код, который не делает то, что говорит» - PullRequest
5 голосов
/ 08 декабря 2010

Я искал документацию по делегированию и протоколу 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;
}

...

Заранее спасибо.

1 Ответ

3 голосов
/ 08 декабря 2010

Проблема в том, что у вас есть два метода с одинаковой сигнатурой метода -initWithDelegate:, но с разными типами в их аргументах и ​​/ или типах возвращаемых данных. Компилятор не может обработать этот случай очень хорошо, и в некоторых случаях это также может привести к ошибкам во время выполнения (не в вашем случае, потому что типы в ваших методах не отличаются по размеру, они все указатели).

Причина этого (AFAIK) заключается в том, что среда выполнения не имеет прямого доступа к типам, используемым в методе. Он просто читает селектор (который не содержит информации о типе) и на основе этого селектора решает, какой метод вызывать. Чтобы помочь среде выполнения упаковать аргументы метода в стек, во время компиляции компилятор создает таблицу, которая сопоставляет селекторы с типами аргументов и возвращаемых значений. Эта таблица имеет только одну запись для каждого селектора. Таким образом, если существуют два метода, которые имеют один и тот же селектор, но разные типы в аргументах или возвращаемом значении, эта система может дать сбой.

В вашем случае:

-init... методы всегда должны возвращать id, а не определенный тип.

Это решает проблему различных типов возвращаемых данных. Другая проблема (разные типы аргументов) труднее решить. Вы можете либо опустить спецификацию протокола из объявления вашего метода (initWithDelegate:(id)delegate), либо дать двум методам разные имена:

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