9

Я люблю блоки, и они очень классные.Как создать блок, который «обертывает» пару целевых/селекторов?

Однако я обнаружил, что блоки могут загромождать мой код и затруднять его чтение, не сворачивая их внутри Xcode (что мне не нравится делать).

Мне нравится разбивать мой код на логические методы (селекторы), чтобы его было легче читать, но он кажется (на первый взгляд), что это невозможно с такими фреймворками, как отправка, AFNetworking и несколько других.

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

Таким образом, не написав кучу клея кода, как это:

-(void) reloadData { 
    ... 
    [[EventsManager instance] data:YES async:^(NSArray *events) { [self processEvents:events] }]; 
    ... 
} 

я мог бы вместо этого сделать что-то вроде этого:

-(void) reloadData { 
    ... 
    [[EventsManager instance] data:YES async:createBlock(self, @selector(processEvents:))]; 
    ... 
} 

Что легче читать (для меня).

С мощностью, которую мы имеем с объективом-c, и это время работы, это должно быть возможным, нет? Однако я не видел ничего подобного.

+0

Просто заметьте, кто читает это, кто может быть смущен: я ответил на свой вопрос, так как сам понял это. –

+1

Вы проработали его в нулевые минуты, неплохая работа! – Wain

+0

@Wain трудно. Принял у меня около 2 дней солидной работы, чтобы получить это. –

ответ

6

Да, это действительно возможно, но это решение является ABI-специфичным (не гарантируется для работы на всех платформах) и широко использует информацию, доступную во время работы о методах.

Мы должны сначала получить информацию о методе, который мы обмениваем блоком. Это делается с помощью NSMethodSignature, который содержит такую ​​информацию, как:

  • Количество аргументов
  • Размер (в байтах) каждого аргумента
  • Размер возвратного типа

Это позволяет обернуть (почти) любого метода без специального кода для этого метода, тем самым создавая повторно используемую функцию.

Во-вторых, нам нужен способ безопасно отправлять вызовы методов во время выполнения. Мы делаем это через NSInvocation, что дает нам возможность создавать динамический и безопасный вызов метода во время выполнения.

В-третьих, нам нужно иметь блок, который может принимать любое количество аргументов, переданных и отправку. Это делается с помощью API-интерфейсов C va_list и должно работать на 99% методов.

Наконец, нам нужно получить возвращаемое значение и вернуть его из нашего блока. Это часть всей операции, которая может не работать, из-за странности с возвращающимися структурами и т. Д. С помощью среды выполнения Objective-C.

Однако, если вы придерживаетесь примитивных типов и объектов Objective-C, этот код должен отлично работать для вас.

Несколько вещей, чтобы отметить о такой реализации:

  • Это зависит от неопределенного поведения с отливкой блока & типов функций, однако, из-за соглашение о вызовах прошивки и Mac, это не должно возникают проблемы (если у вашего метода нет другого типа возврата, кроме того, что ожидает блок).

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

Без дальнейших церемоний, вот пример кода, с последующим осуществлением:


@interface MyObj : NSObject 

-(void) doSomething; 

@end 

@implementation MyObj 

-(void) doSomething 
{ 
    NSLog(@"This is me, doing something! %p", self); 
} 

-(id) doSomethingWithArgs:(long) arg :(short) arg2{ 
    return [NSString stringWithFormat:@"%ld %d", arg, arg2]; 
} 

@end 

int main() { 
    // try out our selector wrapping 
    MyObj *obj = [MyObj new]; 

    id (^asBlock)(long, short) = createBlock(obj, @selector(doSomethingWithArgs::)); 
    NSLog(@"%@", asBlock(123456789, 456)); 
} 

/* WARNING, ABI SPECIFIC, BLAH BLAH BLAH NOT PORTABLE! */ 
static inline void getArgFromListOfSize(va_list *args, void *first, size_t size, size_t align, void *dst, BOOL isFirst) { 
    // create a map of sizes to types 
    switch (size) { 
      // varargs are weird, and are aligned to 32 bit boundaries. We still only copy the size needed, though. 
      // these cases should cover all 32 bit pointers (iOS), boolean values, and floats too. 
     case sizeof(uint8_t): { 
      uint8_t tmp = isFirst ? (uint32_t) first : va_arg(*args, uint32_t); 
      memcpy(dst, &tmp, size); 
      break; 
     } 

     case sizeof(uint16_t): { 
      uint16_t tmp = isFirst ? (uint32_t) first : va_arg(*args, uint32_t); 
      memcpy(dst, &tmp, size); 
      break; 
     } 

     case sizeof(uint32_t): { 
      uint32_t tmp = isFirst ? (uint32_t) first : va_arg(*args, uint32_t); 
      memcpy(dst, &tmp, size); 
      break; 
     } 

      // this should cover 64 bit pointers (Mac), and longs, and doubles 
     case sizeof(uint64_t): { 
      uint64_t tmp = isFirst ? (uint64_t) first : va_arg(*args, uint64_t); 
      memcpy(dst, &tmp, size); 
      break; 
     } 
      /* This has to be commented out to work on iOS (as CGSizes are 64 bits) 
      // common 'other' types (covers CGSize, CGPoint) 
     case sizeof(CGPoint): { 
      CGPoint tmp = isFirst ? *(CGPoint *) &first : va_arg(*args, CGPoint); 
      memcpy(dst, &tmp, size); 
      break; 
     } 
      */ 

      // CGRects are fairly common on iOS, so we'll include those as well 
     case sizeof(CGRect): { 
      CGRect tmp = isFirst ? *(CGRect *) &first : va_arg(*args, CGRect); 
      memcpy(dst, &tmp, size); 
      break; 
     } 

     default: { 
      fprintf(stderr, "WARNING! Could not bind parameter of size %zu, unkown type! Going to have problems down the road!", size); 
      break; 
     } 
    } 
} 

id createBlock(id self, SEL _cmd) { 
    NSMethodSignature *methodSig = [self methodSignatureForSelector:_cmd]; 

    if (methodSig == nil) 
     return nil; 

    return ^(void *arg, ...) { 
     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig]; 

     [invocation setTarget:self]; 
     [invocation setSelector:_cmd]; 

     NSUInteger argc = [methodSig numberOfArguments]; 
     va_list args; 
     va_start(args, arg); 

     for (int argi = 2; argi < argc; argi++) { 
      const char *type = [methodSig getArgumentTypeAtIndex:argi]; 

      NSUInteger size; 
      NSUInteger align; 

      // get the size 
      NSGetSizeAndAlignment(type, &size, &align); 

      // find the right type 
      void *argument = alloca(size); 

      getArgFromListOfSize(&args, arg, size, align, argument, argi == 2); 

      [invocation setArgument:argument atIndex:argi]; 
     } 

     va_end(args); 

     [invocation invoke]; 

     // get the return value 
     if (methodSig.methodReturnLength != 0) { 
      void *retVal = alloca(methodSig.methodReturnLength); 
      [invocation getReturnValue:retVal]; 

      return *((void **) retVal); 
     } 

     return nil; 
    }; 
} 

Позвольте мне знать, если у вас есть какие-либо проблемы с эта реализация!

+1

Кажется, это ужасно много волшебства (но это довольно круто, я не смотрел на реализацию в деталях, но он выглядит солидно). Обратите внимание, что обращение к сайтам не varargs вызывает переменные аргументы при вызове - это нарушение C ABI и * будет * ломаться на некоторых архитектурах с определенными списками аргументов. – bbum

+0

@bbum это правильно, и я включил в свой ответ, что на арках, где objc, скорее всего, будет использоваться (x86, ARM), это не проблема. Фактически, я использовал то же самое в своем знаменитом «приложении iOS в C». –

+0

первый arg не должен быть объявлен 'id', так как ARC попытается сохранить его, и если это не' id', он сработает – newacct

14

Мне понравился ваш ответ с академической точки зрения; +1 и, очевидно, вы что-то узнали.

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

Преимущество этого в том, что это именно Явный:

-(void) reloadData { 
    ... 
    [[EventsManager instance] data:YES async:^(NSArray *events) { [self processEvents:events] }]; 
    ... 
} 

Чтение, что один видит, что асинхронный блок обратного вызова требуется обрабатывать аргументы и что processEvents: метод на self будет использоваться, чтобы сделать фактическая работа.

Выражение createBlock(self, @selector(processEvents:)) представляет собой представление о потерях того же самого; он теряет явную аргументацию обратного вызова и сопоставление между этой аргументацией и вызываемым методом (я часто вижу блоки обратного вызова, подобные описанным выше, с несколькими аргументами, где перед вызовом метода выполняется некоторая легкая логика и/или обработка аргументов).

Обратите также внимание, что обработка сайта вызова без varargs в качестве varargs при вызове является нарушением стандарта C и не будет работать над некоторыми ABI с определенными списками аргументов.

+8

@ RichardJ.RossIII - Я не знаю, что нам нужно быть настолько строгим здесь. Он добавил некоторую полезную информацию к вашему ответу на вопрос, поэтому я не думаю, что это может считаться сломанным окном, которое нужно удалить. Его ответ не будет работать в комментарии, поэтому у меня нет проблем с этим. –

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