2012-12-04 19 views
5

Tôi có một biến với một khối chấp nhận một số đối số. Số lượng đối số chính xác và các loại đối số của chúng có thể thay đổi. Ví dụ nó có thể là một khốiChặn cuộc gọi với các đối số từ va_list khi số đối số và loại của khối có thể thay đổi

void(^testBlock1)(int) = ^(int i){} 

hoặc một khối

void(^testBlock2)(NSString *,BOOL,int,float) = ^(NSString *str,BOOL b,int i,float f){} 

loại Đối số được giới hạn {id, BOOL, char, int, unsigned int, float}.

Tôi biết số lượng đối số hiện tại và loại đối số của chúng. Tôi cần phải thực hiện một phương pháp mà có thể thực hiện khối với lập luận đưa ra:

-(void)runBlock:(id)block withArguments:(va_list)arguments 
      types:(const char *)types count:(NSUInteger)count; 

tôi có một giải pháp làm việc ngây thơ, nhưng nó là khá xấu xí, hỗ trợ các loại duy nhất của không có kích thước hơn 4 byte và dựa trên sự liên kết. Vì vậy, tôi đang tìm kiếm một cái gì đó tốt hơn. Giải pháp của tôi là một cái gì đó như:

#define MAX_ARGS_COUNT 5 
-(void)runBlock:(id)block withArguments:(va_list)arguments 
      types:(const char *)types count:(NSUInteger)count{ 

    // We will store arguments in this array. 
    void * args_table[MAX_ARGS_COUNT]; 

    // Filling array with arguments 
    for (int i=0; i<count; ++i) { 
     switch (types[i]) { 
      case '@': 
      case 'c': 
      case 'i': 
      case 'I': 
       args_table[i] = (void *)(va_arg(arguments, int)); 
       break; 
      case 'f': 
       *((float *)(args_table+i)) = (float)(va_arg(arguments, double)); 
       break; 
      default: 
       @throw [NSException exceptionWithName:@"runBlock" reason:[NSString stringWithFormat:@"unsupported type %c",types[i]] userInfo:nil]; 
       break; 
     } 
    } 

    // Now we need to call our block with appropriate count of arguments 

#define ARG(N) args_table[N] 

#define BLOCK_ARG1 void(^)(void *) 
#define BLOCK_ARG2 void(^)(void *,void *) 
#define BLOCK_ARG3 void(^)(void *,void *,void *) 
#define BLOCK_ARG4 void(^)(void *,void *,void *,void *) 
#define BLOCK_ARG5 void(^)(void *,void *,void *,void *,void *) 
#define BLOCK_ARG(N) BLOCK_ARG##N 

    switch (count) { 
     case 1: 
      ((BLOCK_ARG(1))block)(ARG(0)); 
      break; 
     case 2: 
      ((BLOCK_ARG(2))block)(ARG(0),ARG(1)); 
      break; 
     case 3: 
      ((BLOCK_ARG(3))block)(ARG(0),ARG(1),ARG(2)); 
      break; 
     case 4: 
      ((BLOCK_ARG(4))block)(ARG(0),ARG(1),ARG(2),ARG(3)); 
      break; 
     case 5: 
      ((BLOCK_ARG(5))block)(ARG(0),ARG(1),ARG(2),ARG(3),ARG(4)); 
      break; 
     default: 
      break; 
    } 
} 

Trả lời

6

Vâng, bạn đang chạy lên chống lại cổ điển thiếu-of-siêu dữ liệu và vấn đề ABI trong C đây. Dựa trên Mike232 Awesome Article about MABlockClosure, tôi nghĩ rằng bạn có thể kiểm tra cấu trúc cơ bản của khối và giả định rằng va_list khớp với những gì khối mong đợi. Bạn có thể bỏ khối để struct Block_layout, sau đó block-> descriptor sẽ cung cấp cho bạn cấu trúc BlockDescriptor. Sau đó, bạn có chuỗi @encode đại diện cho các đối số và kiểu của khối (@encode là một toàn bộ các sâu khác). Vì vậy, một khi bạn có danh sách các đối số và kiểu của chúng, bạn có thể đào sâu vào block_layout, lấy invoke, sau đó coi nó như một con trỏ hàm trong đó tham số đầu tiên là khối cung cấp ngữ cảnh. Mike Ash cũng có một số thông tin về Trampolining Blocks có thể hoạt động nếu bạn không quan tâm đến bất kỳ thông tin kiểu nào, nhưng chỉ muốn gọi khối đó.

Hãy để tôi thêm một cảnh báo chất béo "Có con rồng ở đây". Điều này rất nhạy cảm, thay đổi tùy theo ABI và dựa trên các tính năng tối nghĩa và/hoặc không có giấy tờ. Nó cũng có vẻ như bạn chỉ có thể gọi khối trực tiếp bất cứ nơi nào nó cần thiết, có thể sử dụng một NSArray là tham số duy nhất và id là kiểu trả về. Sau đó, bạn không phải lo lắng về bất kỳ "thông minh" hacks backfiring vào bạn.

chỉnh sửa: Bạn có thể sử dụng chữ ký NSMethodSignatureWithObjCTypes :, chuyển vào chữ ký của khối. Sau đó, bạn có thể gọi invocationWithMethodSignature của NSInvocation :, nhưng bạn sẽ phải gọi phương thức invokeWithIMP: riêng để kích hoạt nó bởi vì bạn không có bộ chọn. Bạn sẽ thiết lập mục tiêu cho khối, sau đó invokeWithIMP, truyền con trỏ gọi của Block struct. Xem Generic Block Proxying

+1

Cảm ơn bạn đã trả lời. Tôi đã có danh sách các đối số và loại của chúng. Và có, tôi có thể tìm và lấy con trỏ hàm _invoke_. Nhưng làm thế nào tôi có thể đặc biệt gọi nó trong một cách tốt hơn so với các macro 'BLOCK_ARG (N)' của tôi để đúc và một mảng tạm thời cho các đối số? Có giải pháp nào khác ngoại trừ 'ffi_call'? Sử dụng ** libffi ** trông quá chi phí cho công việc nhỏ của tôi. – Yan

+0

Vâng đây là điều - bạn phải sử dụng lắp ráp để thiết lập các thông số theo cách C mong đợi. Đó là những gì NSInvocation và các ruột khác của thời gian chạy Objective-C làm bởi vì trong C các đối số phải được đặt ra trong các thanh ghi cụ thể, tràn vào ngăn xếp theo một cách nào đó. Điều đó thay đổi dựa trên nền tảng (x86, x64, ARM). – russbishop

0

Một lựa chọn tốt để sử dụng phương thức invokeWithIMP riêng: là làm cho phương thức bạn muốn triển khai khác nhau trở nên phức tạp và làm cho nó tìm kiếm IMP mong muốn bất cứ khi nào nó được gọi. Chúng tôi sử dụng một cái gì đó tương tự trong: https://github.com/tuenti/TMInstanceMethodSwizzler

0

Giải pháp thực tế là có một khối với thông số va_list và để cho khối tự sắp xếp. Đó là một phương pháp đã được chứng minh và đơn giản.