首先我們要了解block的真實結構如下:參考:這里
1 struct ZBBlockLiteral { 2 void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock 3 int flags; 4 int reserved; 5 void (*invoke)(void *, ...); 6 struct block_descriptor { 7 unsigned long int reserved; // NULL 8 unsigned long int size; // sizeof(struct Block_literal_1) 9 // optional helper functions 10 void (*copy_helper)(void *dst, void *src); // IFF (1<<25) 11 void (*dispose_helper)(void *src); // IFF (1<<25) 12 // required ABI.2010.3.16 13 const char *signature; // IFF (1<<30) 14 } *descriptor; 15 // imported variables 16 }; 17 18 // flags enum 19 enum { 20 ZBBlockDescriptionFlagsHasCopyDispose = (1 << 25), 21 ZBBlockDescriptionFlagsHasCtor = (1 << 26), // helpers have C++ code 22 ZBBlockDescriptionFlagsIsGlobal = (1 << 28), 23 ZBBlockDescriptionFlagsHasStret = (1 << 29), // IFF BLOCK_HAS_SIGNATURE 24 ZBBlockDescriptionFlagsHasSignature = (1 << 30) 25 }; 26 typedef int ZBBlockDescriptionFlags;
block其實就是個struct類型,其中的descriptor中的signature正是我們想要的block的方法簽名。那么我們只需要獲取到signature,然后轉換成NSMethodSignature,即可輕易的獲取到我們想要的block的參數和返回值類型,並且通過NSInvocation執行block。
以下就是如何獲取signature的方法:
1 + (NSMethodSignature *)getSignatureWithBlock:(id)block{ 2 struct ZBBlockLiteral *blockRef = (__bridge struct ZBBlockLiteral *)block; 3 ZBBlockDescriptionFlags _flags = blockRef->flags; 4 if (_flags & ZBBlockDescriptionFlagsHasSignature) { 5 void *signatureLocation = blockRef->descriptor; 6 signatureLocation += sizeof(unsigned long int); 7 signatureLocation += sizeof(unsigned long int); 8 9 if (_flags & ZBBlockDescriptionFlagsHasCopyDispose) { 10 signatureLocation += sizeof(void(*)(void *dst, void *src)); 11 signatureLocation += sizeof(void (*)(void *src)); 12 } 13 14 const char *signature = (*(const char **)signatureLocation); 15 return [NSMethodSignature signatureWithObjCTypes:signature]; 16 } 17 return nil; 18 }
其中 signatureLocation += sizeof(unsigned long int);的加號,就是c/c++中的指針移動,通過指針移動,我們就可以找到signature的指針。然后通過[NSMethodSignature signatureWithObjCTypes:signature]構建方法簽名的objc對象。
下面是測試代碼:
1 BOOL(^testBlock)(NSString *, NSString *) = ^(NSString *str1, NSString *str2) { 2 NSLog(@"-----:%@-------:%@", str1, str2); 3 return [str1 isEqualToString:str2]; 4 }; 5 6 NSMethodSignature *sign = [MyBlockDesc getSignatureWithBlock:testBlock]; 7 8 if (sign) { 9 10 NSLog(@"----參數個數:%@", @(sign.numberOfArguments)); 11 NSLog(@"----返回值類型:%@", [NSString stringWithUTF8String:sign.methodReturnType]); 12 for (int i=0; i<sign.numberOfArguments; i++) { 13 NSLog(@"----第%@個參數:%@", @(i), [NSString stringWithUTF8String:[sign getArgumentTypeAtIndex:i]]); 14 } 15 NSString *str1 = @"111"; 16 NSString *str2 = @"111"; 17 NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sign]; 18 [inv setArgument:&str1 atIndex:1]; 19 [inv setArgument:&str2 atIndex:2]; 20 inv.target = testBlock; 21 [inv invoke]; 22 BOOL res; 23 [inv getReturnValue:&res]; 24 25 NSLog(@"----返回結果:%@", @(res)); 26 27 }else { 28 NSLog(@"------參數簽名為空"); 29 }
打印日志如下:
1 2020-01-09 09:32:04.600723+0800 Test[2503:27368] ----參數個數:3 2 2020-01-09 09:32:04.600908+0800 Test[2503:27368] ----返回值類型:B 3 2020-01-09 09:32:04.601046+0800 Test[2503:27368] ----第0個參數:@? 4 2020-01-09 09:32:04.601166+0800 Test[2503:27368] ----第1個參數:@"NSString" 5 2020-01-09 09:32:04.601277+0800 Test[2503:27368] ----第2個參數:@"NSString" 6 2020-01-09 09:32:04.601380+0800 Test[2503:27368] -----:111-------:111 7 2020-01-09 09:32:04.601493+0800 Test[2503:27368] ----返回結果:1
其中第0個參數就是block對象自身。因此我們既可以通過如上手段,來擴展實現一個線程安全的block調用,結合可變參數的使用,就可以實現任意參數的block的調用。