2013-03-15 3 views
5

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

Так что я думаю ... Возможно ли, используя сумасшедшую силу ObjC-, сделать что-то подобное?

// id _delegate; // Most likely declared as class variable or it will be released 
    _delegate = [DelegateFactory delegateOfProtocol:@protocol(SomeProtocol)]; 
    _delegate performBlock:^{ 
     // Do something 
    } onSelector:@selector(someProtocolMethod)]; // would execute the given block when the given selector is called on the dynamic delegate object. 
    theObject.delegate = (id<SomeProtocol>)_delegate; 
    // Profit! 

performBlock:onSelector:

Если YES, как? И есть ли причина, по которой мы не должны делать это как можно больше?

Редактировать

Похоже, что это возможно. В текущих ответах основное внимание уделяется первой части вопроса, как это делается. Но было бы неплохо провести некоторое обсуждение «, если мы сделаем это» ».

+0

Другими словами, я хотел бы реализовать анонимные классы Java с блоками. – hpique

+0

Это возможно, но в общем случае требуется перейти от 'NSInvocation' к блоку через libffi. У меня есть класс, плавающий вокруг на моем жестком диске, который делает именно то, что находится в вашем фрагменте кода, но я уезжаю из города на неделю и не смогу опубликовать много кода, пока не вернусь. –

+0

@JoshCaswell Neat. Не торопись. :) – hpique

ответ

2

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

typedef void *(^UBDCallback)(id); 
typedef void(^UBDCallbackStret)(void *, id); 

void *UBDDelegateMethod(UniversalBlockDelegate *self, SEL _cmd, id sender) 
{ 
    UBDCallback cb = [self blockForSelector:_cmd]; 
    return cb(sender); 
} 

void UBDelegateMethodStret(void *retadrr, UniversalBlockDelegate *self, SEL _cmd, id sender) 
{ 
    UBDCallbackStret cb = [self blockForSelector:_cmd]; 
    cb(retaddr, sender); 
} 

@interface UniversalBlockDelegate: NSObject 

- (BOOL)addDelegateSelector:(SEL)sel isStret:(BOOL)stret methodSignature:(const char *)mSig block:(id)block; 

@end 

@implementation UniversalBlockDelegate { 
    SEL selectors[128]; 
    id blocks[128]; 
    int count; 
} 

- (id)blockForSelector:(SEL)sel 
{ 
    int idx = -1; 
    for (int i = 0; i < count; i++) { 
     if (selectors[i] == sel) { 
      return blocks[i]; 
     } 
    } 

    return nil; 
} 

- (void)dealloc 
{ 
    for (int i = 0; i < count; i++) { 
     [blocks[i] release]; 
    } 
    [super dealloc]; 
} 

- (BOOL)addDelegateSelector:(SEL)sel isStret:(BOOL)stret methodSignature:(const char *)mSig block:(id)block 
{ 
    if (count >= 128) return NO; 

    selectors[count] = sel; 
    blocks[count++] = [block copy]; 

    class_addMethod(self.class, sel, (IMP)(stret ? UBDDelegateMethodStret : UBDDelegateMethod), mSig); 

    return YES; 
} 

@end 

Использование:

UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero]; 
UniversalBlockDelegate *d = [[UniversalBlockDelegate alloc] init]; 
webView.delegate = d; 
[d addDelegateSelector:@selector(webViewDidFinishLoading:) isStret:NO methodSignature:"[email protected]:@" block:^(id webView) { 
    NSLog(@"Web View '%@' finished loading!", webView); 
}]; 
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://google.com"]]]; 
+0

Вы предполагаете, что я пишу класс с делегатом. Если бы это было так, это не было бы проблемой. Я уточню это. – hpique

+0

@hpique я вижу. Итак, в основном вы хотите, чтобы блок вызывался, а делегат отправил сообщение? – 2013-03-15 17:27:58

+0

by 'performBlock: onSelector:' Я хотел выполнить данный блок, когда данный селектор вызывается в объекте '_delegate'. – hpique

5

Я просто собрать небольшой проект, который позволяет сделать именно это ...

@interface EJHDelegateObject : NSObject 

+ (id)delegateObjectForProtocol:(Protocol*) protocol; 

@property (nonatomic, strong) Protocol *protocol; 
- (void)addImplementation:(id)blockImplementation forSelector:(SEL)selector; 

@end 


@implementation EJHDelegateObject 
static NSInteger counter; 

+ (id)delegateObjectForProtocol:(Protocol *)protocol 
{ 
    NSString *className = [NSString stringWithFormat:@"%s%@%i",protocol_getName(protocol),@"_EJH_implementation_", counter++]; 
    Class protocolClass = objc_allocateClassPair([EJHDelegateObject class], [className cStringUsingEncoding:NSUTF8StringEncoding], 0); 
    class_addProtocol(protocolClass, protocol); 
    objc_registerClassPair(protocolClass); 
    EJHDelegateObject *object = [[protocolClass alloc] init]; 
    object.protocol = protocol; 
    return object; 
} 


- (void)addImplementation:(id)blockImplementation forSelector:(SEL)selector 
{ 
    unsigned int outCount; 
    struct objc_method_description *methodDescriptions = protocol_copyMethodDescriptionList(self.protocol, NO, YES, &outCount); 
    struct objc_method_description description; 
    BOOL descriptionFound = NO; 
    for (int i = 0; i < outCount; i++){ 
     description = methodDescriptions[i]; 
     if (description.name == selector){ 
      descriptionFound = YES; 
      break; 
     } 
    } 
    if (descriptionFound){ 
     class_addMethod([self class], selector, imp_implementationWithBlock(blockImplementation), description.types); 
    } 
} 

@end 

И используя EJHDelegateObject:

self.alertViewDelegate = [EJHDelegateObject delegateObjectForProtocol:@protocol(UIAlertViewDelegate)]; 
[self.alertViewDelegate addImplementation:^(id _self, UIAlertView* alertView, NSInteger buttonIndex){ 
    NSLog(@"%@ dismissed with index %i", alertView, buttonIndex); 
} forSelector:@selector(alertView:didDismissWithButtonIndex:)]; 

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Example" message:@"My delegate is an EJHDelegateObject" delegate:self.alertViewDelegate cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil]; 
[alertView show]; 
+0

https://github.com/ehopealot/EJHDelegateObject – ehope

+0

Это не сработает. Первым параметром блока должен быть объект, на который был вызван метод (в данном случае объект 'whatever_EJH_implementation_') – newacct

+0

@newacct imp_implementationWithBlock обрабатывает то, что для нас – ehope

9

Хорошо, я, наконец, добрался до WoolDelegate на GitHub. Теперь мне понадобится только месяц, чтобы написать правильный README (хотя я думаю, что это хороший старт).

Сам класс делегата довольно прост. Он просто поддерживает сопоставление словаря SEL s для блокировки. Когда экземпляр recieves сообщение, к которому он не отвечает, он заканчивает в forwardInvocation: и смотрит в словарь для выбора:

- (void)forwardInvocation:(NSInvocation *)anInvocation { 

    SEL sel = [anInvocation selector]; 
    GenericBlock handler = [self handlerForSelector:sel]; 

Если он найден, указатель вызова функции Блока вытянут и прошел вдоль до сочных бит:

IMP handlerIMP = BlockIMP(handler); 

    [anInvocation Wool_invokeUsingIMP:handlerIMP]; 
} 

(BlockIMP() функции, наряду с другим блоком-зондирующего кодом, благодаря Mike Ash на самом деле, многое из этого проекта строится на материале, который я узнал от его пятницы Q & в.. Если вы не читали эти эссе, вам не хватает.)

Следует отметить, что это происходит через механизм обработки полного метода каждый раз, когда отправляется конкретное сообщение; там есть скорость. Альтернативой является путь, которым занимались Эрик Х. и EMKPantry, который создает новый клас для каждого объекта-делегата, который вам нужен, и используя class_addMethod().Поскольку каждый экземпляр WoolDelegate имеет свой собственный словарь обработчиков, нам не нужно это делать, но, с другой стороны, нет возможности «кэшировать» поиск или вызов. Метод может быть добавлен только к классу , а не к экземпляру.

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

Перемещение, мясо этой процедуры фактически находится в NSInvocation category, что находится в проекте. Это использует libffi для вызова функции, которая неизвестна до выполнения - вызов Блока - с аргументами, которые также неизвестны до времени выполнения (которые доступны через NSInvocation). Как правило, это невозможно, по той же причине, что нельзя передать va_list: компилятор должен знать, сколько аргументов есть и насколько они велики. libffi содержит ассемблер для каждой платформы, который знает/основан на этих платформах calling conventions.

Здесь три шага: libffi нуждается в списке типов аргументов вызываемой функции; ему нужны сами значения аргументов в конкретный формат; то функция (указатель вызова блока) должна быть вызвана через libffi, а возвращаемое значение возвращается обратно в NSInvocation.

Реальная работа для первой части обрабатывается в основном функцией, которая снова написана Майком Эшем, вызванным от Wool_buildFFIArgTypeList. libffi имеет внутренний struct s, который используется для описания типов аргументов функции. При подготовке вызова функции библиотека нуждается в списке указателей на эти структуры. NSMethodSignature для NSInvocation допускает доступ к строке кодирования каждого аргумента; перевод оттуда к правильному ffi_type обрабатывается набором if/else поисков:

arg_types[i] = libffi_type_for_objc_encoding([sig getArgumentTypeAtIndex:actual_arg_idx]); 

... 

if(str[0] == @encode(type)[0]) \ 
{ \ 
    if(sizeof(type) == 1) \ 
     return &ffi_type_sint8; \ 
    else if(sizeof(type) == 2) \ 
     return &ffi_type_sint16; \ 

Далее libffi хочет указатели на аргумент сами значения. Это делается в Wool_buildArgValList: получить размер каждого аргумента, опять-таки из NSMethodSignature и выделить кусок памяти, что размер, а затем возвращает список:

NSUInteger arg_size; 
NSGetSizeAndAlignment([sig getArgumentTypeAtIndex:actual_arg_idx], 
         &arg_size, 
         NULL); 
/* Get a piece of memory that size and put its address in the list. */ 
arg_list[i] = [self Wool_allocate:arg_size]; 
/* Put the value into the allocated spot. */ 
[self getArgument:arg_list[i] atIndex:actual_arg_idx]; 

(В сторону: есть несколько заметок в коде о пропуская через SEL, который является (скрытым) вторым переданным аргументом для любого вызова метода. Указатель вызова Блока не имеет слота для хранения SEL, он просто сам по себе является первым аргументом, а остальные являются «нормальными» «Поскольку блок, как написано в клиентском коде, никогда не мог получить доступ к этому аргументу в любом случае (он не существует в то время), я решил проигнорировать его.)

libffi теперь нужно сделать «prep»; до тех пор, как успешно (и пространство для возвращаемого значения может быть выделено), указатель вызова функции теперь может быть «называется», а возвращаемое значение может быть установлено:

ffi_call(&inv_cif, (genericfunc)theIMP, ret_val, arg_vals); 
if(ret_val){ 
    [self setReturnValue:ret_val]; 
    free(ret_val); 
} 

Там в некоторых демонстрациях функциональных возможностей в main.m в проекте.

Наконец, что касается вашего вопроса о том, «должно ли это быть сделано?», Я думаю, что ответ «да, пока он делает вас более продуктивным».WoolDelegate является полностью общим, и экземпляр может действовать как любой полностью выписанный класс. Однако мое намерение заключалось в том, чтобы сделать простых, единовременных делегатов - для этого нужны только один или два метода, и им не нужно проживать мимо их делегатов - меньше работы, чем писать совершенно новый класс, и более разборчивым/поддерживаемый, чем приклеить некоторые методы делегата в контроллер просмотра, потому что это самое простое место для их размещения. Воспользовавшись временем выполнения и динамизмом языка, как это, мы надеемся, может повысить читаемость вашего кода, точно так же, например, Block-based NSNotification handlers.

Смежные вопросы