Как заменить набор методов делегата блоками

У меня есть объект, который определяет набор протоколов и методов делегирования.

Другой объект (ы) отвечает на них в обычном шаблоне делегата шаблона.

   @interface object
   @property (nonatomic,weak) id <myProtocol> delegate;
   @end

object.delegate = someObject;

@interface someObject <object delegate>
// some object delegate methods and their implementation
@end

Во многих случаях - хотелось бы изменить поведение того или иного метода делегата (реализация протокола)

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

Но в стандартном шаблоне делегата это невозможно. Одним из способов решения проблемы было бы создание большой таблицы диспетчеризации (оператор if или switch внутри отвечающего делегата), но это было бы неудобно и очень усложняло бы понимание кода.

было бы намного проще написать что-то вроде

//standard case
theObject.delegate.blockForMethodOne = ^{the usual code to run} // perhaps how to update a UITableView

if (some condition happened) //something was selected something for instance
{
   theObject.delegate.blockForMethodOne = ^ { some code to run in that case }
}

Без такого синтаксиса нам пришлось бы написать что-то подобное

-(void)methodOne
{
  if (standard case)
  {
    //standard code
  }
  else if (self.someConditionHappended) // awkward variable in the object to track changes
  {
     // the code in this case
  }
  // and so on
}

Я видел ответы, но они недостаточно хороши .

Что-то, что могло бы динамически генерировать селектор и блокировку, было бы намного лучше (прокси-делегат)

Любая идея, как создать это?

Изменить:

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

@interface DelegateManager : NSObject

@property (nonatomic,weak) id proxiedObject;
@property (nonatomic) BOOL justResponded;
@property (nonatomic) BOOL logOnNoResponse;

-(id)init;
-(void)forwardInvocation:(NSInvocation*)invocation;
-(id)proxiedObject;
-(void)setProxiedObject:(id)proxied;
-(BOOL)justResponded;
-(void)setLogOnNoResponse:(BOOL)log;
-(BOOL)logOnNoResponse;



@end

@interface NSMethodSignature (objctypes)
+(NSMethodSignature*)signatureWithObjCTypes:(const char*)types;
@end

@implementation DelegateManager
-(id)init
{
    self = [super init];
    if (self)
    {
        self.proxiedObject   = nil;
        self.justResponded   = NO;
        self.logOnNoResponse = NO;
    }

    return self;
}

-(NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature *sig;
    sig=[[self.proxiedObject class] instanceMethodSignatureForSelector:selector];
    if(sig==nil)
    {
        // sig=[NSMethodSignature signatureWithObjCTypes:"@^v^c"];
        sig = [[NSObject class] instanceMethodSignatureForSelector: @selector(init)];
    }
    self.justResponded=NO;
    return sig;
}

-(void)forwardInvocation:(NSInvocation*)invocation
{
    if(self.proxiedObject==nil)
    {
        if(self.logOnNoResponse)
            NSLog(@"Warning: proxiedObject is nil! This is a debugging message!");
        return;
    }
    if([self.proxiedObject respondsToSelector:[invocation selector]])
    {
        [invocation invokeWithTarget:self.proxiedObject];
        self.justResponded=YES;
    }
    else if(self.logOnNoResponse)
    {
        NSLog(@"Object \"%@\" failed to respond to delegate message \"%@\"! This is a debugging message.",[[self proxiedObject] class],NSStringFromSelector([invocation selector]));
    }
    return;
}


@end

Этот код работает следующим образом

myobject.delegate = self.delegateManager; // always sets itself to this internal "proxy" for the delegate

-(void)setDelegate(id<myProtocol>)delegate
{
  [self.delegateManager setProxiedObject:delegate];
}

сообщения всегда отправляются на прокси, который попытается передать их дальше

мойОбъект:

[self.delegateManager callADelegateMethod:self];

делегатменеджер отвечает за передачу сообщения вперед

кажется, что это можно было бы расширить, выполнив что-то вроде

if (something happened in my object)
{
// replace the implementation of the selector my delegate supplies
  [self.myObject.delegateManager setBlock:someBlock forSelector:the selector of the delegate];
}

в делегатменеджере

-(void)setBlock:(someBlock)block forSelector:(selector)aSelector
{
   [self.dictionary setObject:block forKey:aSelector];
}

-(void)forwardInvocation:(NSInvocation*)invocation
{
  //check if there is an entrance of a block for the particular invocation
}

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


person Avner Barr    schedule 12.11.2013    source источник


Ответы (2)


Если вы хотите сделать так:

theObject.delegate.blockForMethodOne = ^{the usual code to run}
if (some condition happened)
{
   theObject.delegate.blockForMethodOne = ^ { some code to run in that case }
}

Есть несколько вопросов: собираетесь ли вы устанавливать блок делегата (blockForMethodOne) извне делегата? так откуда он будет вызываться (изнутри делегата?)? при каком условии это будет вызвано? Если я правильно понимаю, Вы хотите задать разное поведение для одного и того же блока по каким-то условиям, но зачем? вы можете просто вызывать разные методы в разных условиях.

Но если Вы знаете, что делаете)) - Вы должны создать класс (например, Delegate), который будет хранить какой-то блок, например:

typedef void (^BlockCallback)(void);

@interface Delegate : NSObject
@property (nonatomic, copy) BlockCallback blockForMethodOne;
@end

он будет хранить в blockForMethodOne все, что вы там установите, но это больше не шаблон делегирования. Установка блока где-то (theObject.delegate.blockForMethodOne = ^{обычный код для запуска}) не запустит его, если вы не переопределите сеттер для 'blockForMethodOne', он работает так же, как обычное свойство (просто сохраните его), поэтому у вас есть добавьте немного логики, чтобы как-то это назвать, на данный момент я не вижу никаких преимуществ вашего подхода с блоками. Также Вы можете разместить @property (неатомарное, копировать) BlockCallback blockForMethodOne; в объявлении протокола, поэтому любой класс, использующий этот протокол, должен иметь 'blockForMethodOne', если хотите)

Надежда помогает

person in.disee    schedule 12.11.2013

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

Скажем, ваш протокол делегата был:

@protocol FooDelegateProtocol <NSObject>
- (void) foo:(Foo*)foo didReceiveData:(NSData*)data;
@end

вы можете переопределить протокол следующим образом

typedef void (^foo_receivedDataBlock)(NSData*);

@protocol FooProtocol <NSObject>
@property (nonatomic, copy) foo_receivedDataBlock receivedDataHandler;
@end

Обратите внимание, что это НЕ протокол для делегата, а протокол для самого класса Foo, который должен его реализовать.

Теперь вместо делегата у вас есть набор свойств, которые являются блоками. Вместо установки делегата объекта класса Foo теперь вы настраиваете каждый из блоков. Для каждого метода делегата теперь есть соответствующий блок.

Теперь за настройку блоков отвечает сайт вызова.

person CouchDeveloper    schedule 12.11.2013