Block
的使用在OC編程中,咱們常常會使用到
block
。它能夠做爲成員變量、函數參數、函數返回值等等。接下來,咱們先來看下block
的一些經常使用方式。html
- (void)blockTest1 {
//無參數無返回值
void(^printBlock)(void) = ^ {
NSLog(@"print block");
};
printBlock();
//有參數無返回值
void(^combineBlock)(NSString *str, NSNumber *num) = ^(NSString *str, NSNumber *num) {
NSString *combineStr = [NSString stringWithFormat:@"%@-%@", str, num];
NSLog(@"combine block: %@", combineStr);
};
combineBlock(@"str", @1);
//有參數有返回值
int(^caculateBlock)(int value1, int value2) = ^(int value1, int value2) {
return value1 + value2;
};
int sum = caculateBlock(1000, 24);
NSLog(@"caculate block: %d", sum);
//定義Block
typedef NSString*(^ConvertBlock)(int value);
ConvertBlock block = ^(int value) {
return [NSString stringWithFormat:@"convert-%d", value];
};
NSString *convertStr = block(1);
NSLog(@"convert block: %@", convertStr);
}
複製代碼
根據上面的例子,能夠看出Block
的定義爲:返回類型 (^Block名稱) (參數類型)
c++
- (void)blockTest2 {
[self doSomethingWithCompletion:^(BOOL success) {
NSLog(@"do something finished with %@", (success ? @"YES" : @"NO"));
}];
}
- (void)doSomethingWithCompletion:(void(^)(BOOL success))completion {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //模擬耗時
if (completion) {
completion(YES);
}
});
}
複製代碼
一般在進行一些異步操做的時候,咱們都會使用block
做爲函數參數來回調結果。git
- (void)blockTest3 {
NSString *(^convertBlock)(void) = [self createBlockWithValue:1];
NSString *convertStr = convertBlock();
NSLog(@"convert block: %@", convertStr);
}
- (NSString *(^)(void))createBlockWithValue:(int)value {
return ^{
return [NSString stringWithFormat:@"str-%d", value];
};
}
複製代碼
一般將block
做爲函數返回值處理的場景會比較少,不事後面講到的鏈式調用就會經過該形式實現。github
@interface BlockViewController : UIViewController
@property (nonatomic, strong) void(^blockTest)(NSString *result);
@end
@implementation BlockViewController
- (void)viewDidLoad {
[super viewDidLoad];
if (self.blockTest) {
self.blockTest(@"block result");
}
}
@end
BlockViewController *viewController = [[BlockViewController alloc] init];
viewController.blockTest = ^(NSString * _Nonnull result) {
NSLog(@"block result: %@", result); //通知外層
};
[self.navigationController pushViewController:viewController animated:YES];
複製代碼
能夠經過設置成員變量爲block
來通知外部調用者,從而達成二者數據的傳遞。objective-c
__block
修飾符- (void)blockTest4 {
int value1 = 1;
void (^onlyreadBlock)(void) = ^ {
NSLog(@"only read block: %d", value1);
};
onlyreadBlock();
__block int value2 = 1;
void(^processBlock)(void) = ^ {
value2 = 2;
NSLog(@"process block: %d", value2);
};
processBlock();
}
複製代碼
當須要在block內修改局部變量時,須要經過__block
修飾符定義,不然只能讀取,不能修改。編程
__weak
和__strong
修飾符- (void)blockTest5 {
__weak typeof(self) weakSelf = self;
[self doSomethingWithCompletion:^(BOOL success) {
[weakSelf doSecondThing];
}];
[self doSomethingWithCompletion:^(BOOL success) {
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSecondThing];
[strongSelf doThirdThing];
}
}];
}
- (void)doSecondThing {
NSLog(@"do second thing");
}
- (void)doThirdThing {
NSLog(@"do third thing");
}
複製代碼
爲了不循環引用,一般在Block
內會將self
轉換爲weakSelf
,但爲何有時候還須要使用strongSelf
呢?好比第一個block中只使用weakSelf
定義,而第二個block卻額外使用了__strongSelf
。數組
其實,這裏主要是沒法肯定block內weakSelf
什麼時候會被釋放掉,對第一個block,若weakSelf
被釋放了,則不會調用doSecondThing
方法,這樣一般並不會致使什麼錯誤發生。但對於第二個block,若是仍是繼續沿用weakSelf
,假設weakSelf
在執行完doSecondThing
後被釋放了,那麼就會致使doThirdThing
方法不會被調用,意味着只執行了一個方法!這樣勢必是很容易引起出一些不可預見的狀況。promise
所以,爲了保證代碼執行的"完整性",block內使用__strong
修飾符,這樣在weakSelf
未被釋放的狀況下進入block後,block
在被執行完前都不會被釋放。具體分析能夠看這篇文章。bash
Block
的內存類型在咱們通常的開發工做中,每每不多會涉及到
Block
的內存類型相關。由於在ARC下,編譯器幫咱們自動處理了關於block
內存相關的操做。不過總有些狀況,須要咱們對Block
的內存概念有所瞭解,否則會致使一些錯誤的發生,好比下面的這個例子。app
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *blocks = [self blockArray];
typedef void(^blk)(void);
for (NSInteger index = 0; index < blocks.count; index ++) {
blk block = blocks[index];
if (block) {
block();
}
NSLog(@"blk-%ld: %@", index, block);
}
}
- (NSArray *)blockArray {
int val = 10;
return [[NSArray alloc] initWithObjects:^(){NSLog(@"blk0: %d", val);}, ^(){NSLog(@"blk1: %d", val);}, ^(){NSLog(@"blk2: %d", val);}, nil];
}
複製代碼
上面的例子,其實就是經過blockArray
方法返回一個包含3個block
的數組,而後遍歷該數組,分別調用block
對象。
運行程序,會發現程序crash了,並提示:
能夠看到當訪問第二個block
時,致使程序crash,顯示EXC_BAD_ACCESS
。一般該錯誤是訪問了野指針,但這裏獲取到的blocks
爲什麼會出現野指針,並且訪問第一個block
的時候是正常的?
能夠先從blocks
的debug信息下手:
debug信息顯示第一個block
爲_NSMallocBlock_
類型,再探究以前,這裏須要瞭解清楚Block
的內存類型了。咱們再看另外一個例子:
- (void)blockTest {
void(^globalBlock)(void) = ^ {
NSLog(@"global block");
};
int value = 1;
void(^stackBlock)(void) = ^ {
NSLog(@"stack block: %d", value);
};
void(^mallocBlock)(void) = [stackBlock copy];
NSArray *blocks = [[NSArray alloc] initWithObjects:globalBlock, stackBlock, mallocBlock, nil];
for (id blk in blocks) {
NSLog(@"blk: %@", blk);
}
}
複製代碼
在運行前,須要在Build Prases -> Compile Sources中找到對應的文件,而後添加-fno-objc-arc
,即對該文件禁用ARC功能。由於ARC會自動對Block
進行一些額外內存處理操做。運行後,能夠看到結果以下:
blk: <__NSGlobalBlock__: 0x10ac45758>
blk: <__NSStackBlock__: 0x7ffee4fe4ea8>
blk: <__NSMallocBlock__: 0x600001d3daa0>
複製代碼
可見,上面的三種block
分別對應Block
的三種不一樣內存類型。
__NSGlobalBlock__
: 存儲在數據段(通常用來存儲全局變量、靜態變量等,直到程序結束時纔會回收)中,block
內未訪問了局部變量
__NSStackBlock__
: 存儲在棧(通常用來存儲局部變量,自動分配內存,當局部變量做用域執行完後會被當即回收)中,block
內訪問了局部變量
__NSMallocBlock__
: 存儲在堆(alloc出來的對象,由開發者進行管理)中,當__NSStackBlock__
進行copy
操做時
清楚Block
的內存類型後,再將文件中-fno-objc-arc
去掉,看看ARC下,結果又是如何?
ARC下結果:
blk: <__NSGlobalBlock__: 0x1087bc758>
blk: <__NSMallocBlock__: 0x600001505a10>
blk: <__NSMallocBlock__: 0x600001505a10>
複製代碼
根據結果,得知第二個stackBlock
也是__NSMallocBlock__
類型,這是由於在ARC
下,當對棧block
進行賦值操做時,編譯器會自動將其拷貝到堆中。
再回到第一個例子,就能解釋爲何會出現野指針問題了:由於blockArray
中建立的block
訪問了局部變量,爲__NSStackBlock__
類型,而這裏並無對block
進行賦值或者copy
,因此ARC下也不會將其拷貝到堆中。所以,當blockArray
做用域結束時,__NSStackBlock__
類型的block也會被自動釋放,而致使後面訪問的block
爲野指針。
- (NSArray *)blockArray {
int val = 10;
id block0 = ^(){NSLog(@"blk0: %d", val);};
id block1 = ^(){NSLog(@"blk1: %d", val);};
id block2 = ^(){NSLog(@"blk2: %d", val);};
return [[NSArray alloc] initWithObjects:[^(){NSLog(@"blk0: %d", val);} copy], [^(){NSLog(@"blk1: %d", val);} copy], [^(){NSLog(@"blk2: %d", val);} copy], nil]; //方式二
//return [[NSArray alloc] initWithObjects:^(){NSLog(@"blk0: %d", val);}, ^(){NSLog(@"blk1: %d", val);}, ^(){NSLog(@"blk2: %d", val);}, nil]; //方式二
}
複製代碼
如上,使用賦值或copy
操做均可以使得block
修改成__NSMallocBlock__
類型,從而能正常訪問。
這裏還有一個問題就是,爲何第一個例子中第一個Block
是__NSMallocBlock__
?筆者這裏猜想應該是initWithObjects:
方法內部會保留第一個對象,即會致使第一個Block
會被賦值,因此會被拷貝到堆中。
Block
的本質咱們知道在OC中,大部分狀況下都是跟
NSObject
對象打交道的,而Block
彷佛是與NSObject
不太同樣的一種存在形式。那麼Block
的本質究竟是怎麼樣呢,接下來將經過一些例子來進行探究。
a. 先在main.m
文件中定義一個block
,並調用它
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
void(^block)(void) = ^ {
NSLog(@"hello world");
};
block();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
複製代碼
b. 而後使用clang插件對main.m
改寫爲cpp
文件
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
複製代碼
生成對應的main.cpp
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5w_hqdc9zg163j32btftjdkh1kw0000gp_T_main_25fc50_mi_0);
}
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
//1. block建立
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//2. 調用block
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
複製代碼
這裏能夠分爲Block
的建立和調用兩步來看,代碼這樣看起來可能會比較難理解,這裏對這兩個步驟整理爲以下:
// 1-1:建立__main_block_impl_0對象
__main_block_impl_0 block_impl = __main_block_impl_0((void *) __main_block_func_0, &__main_block_desc_0_DATA);
// 1-2: 將__main_block_impl_0強制轉換爲函數指針
void(*block)(void) = (void(*)()) &block_impl;
// 2-1:定義另外一個函數指針,並將__main_block_impl_0中的FuncPtr轉換爲定義的函數指針
void(*block_call)(__block_impl *) = (void(*)(__block_impl *)) ((__block_impl *)block)->FuncPtr;
// 2-2: 調用函數指針
block_call((__block_impl *)block);
複製代碼
如上所示,block
的定義實際上就是建立了一個__main_block_impl_0
的對象,而block
的調用,即調用對象的函數指針FuncPtr
。
接下來,咱們來看下__main_block_impl_0
相關信息,這裏對比着Block_layout來看:
//main.cpp
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//Block_private.h
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
typedef void(*BlockInvokeFunction)(void *, ...);
複製代碼
能夠看到生成的__main_block_desc_0
和Block_layout
的結構是一致的。
NSObject
的內部結構的話,應該清楚,它通常是指向其父類Block
的一些信息,後面會講解到#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
複製代碼
仔細查看Block_private.h
文件時,會發現有三種Block_descriptor_x
結構體,而Block_layout
只包含了Block_descriptor_1
,是否其餘兩種就沒用到呢?咱們經過例子來驗證下:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
__block int value = 1;
void (^block)(void) = ^{
value = 2; //修改__block變量
NSLog(@"update value: %d", value);
};
block();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
// 新增
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_value_0 *value; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_value_0 *_value, int flags=0) : value(_value->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
如上所示,block
修改了__block
修飾符的變量,重寫爲c++
代碼後,對比以前的__main_block_desc_0
,新增了copy
和dispose
兩個函數指針。而這兩個函數指針恰好與Block_descriptor_2
的變量對應。由此,能夠看出Block_layout
會根據具體狀況決定是否添加Block_descriptor_2
或Block_descriptor_3
。
總結:經過上面的探究,得知:
Block
的本質爲一個Block_layout
的結構體,也包含了isa
指針,所以,也能夠稱爲OC對象。
__Block
修飾符原理咱們知道一般要在
Block
內修改局部變量,必須得使用__block
修飾局部變量。而對於成員變量和全局變量則直接修改便可。接下來咱們將經過例子來講明__block
是如何實現支持局部變量的修改,以及爲什麼成員變量和全局變量不須要使用__block
修飾。
- (void)_blockTest {
NSInteger readonlyValue = 1;
void(^readonlyBlock)(void) = ^{
NSLog(@"readonly variable : %ld", readonlyValue);
};
readonlyBlock();
void(^memberVariableBlock)(void) = ^{
self.value = 1;
NSLog(@"member variable :%ld", self.value);
};
memberVariableBlock();
static NSInteger globalValue = 0;
void(^globalVariableBlock)(void) = ^{
globalValue = 1;
NSLog(@"global variable :%ld", globalValue);
};
globalVariableBlock();
__block NSInteger localValue = 0;
void(^localVariableBlock)(void) = ^{
localValue = 1;
NSLog(@"local variable :%ld", localValue);
};
localVariableBlock();
}
複製代碼
一樣,咱們先經過clang插件將其重寫爲cpp
文件:
static void _I_BlockTypeViewController__blockTest(BlockTypeViewController * self, SEL _cmd) {
//1.僅訪問局部變量
NSInteger readonlyValue = 1;
void(*readonlyBlock)(void) = ((void (*)())&__BlockTypeViewController___blockTest_block_impl_0((void *)__BlockTypeViewController___blockTest_block_func_0, &__BlockTypeViewController___blockTest_block_desc_0_DATA, readonlyValue));
((void (*)(__block_impl *))((__block_impl *)readonlyBlock)->FuncPtr)((__block_impl *)readonlyBlock);
//2.修改爲員變量
void(*memberVariableBlock)(void) = ((void (*)())&__BlockTypeViewController___blockTest_block_impl_1((void *)__BlockTypeViewController___blockTest_block_func_1, &__BlockTypeViewController___blockTest_block_desc_1_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)memberVariableBlock)->FuncPtr)((__block_impl *)memberVariableBlock);
//3.修改全局變量
static NSInteger globalValue = 0;
void(*globalVariableBlock)(void) = ((void (*)())&__BlockTypeViewController___blockTest_block_impl_2((void *)__BlockTypeViewController___blockTest_block_func_2, &__BlockTypeViewController___blockTest_block_desc_2_DATA, &globalValue));
((void (*)(__block_impl *))((__block_impl *)globalVariableBlock)->FuncPtr)((__block_impl *)globalVariableBlock);
//4.修改局部變量
__attribute__((__blocks__(byref))) __Block_byref_localValue_0 localValue = {(void*)0,(__Block_byref_localValue_0 *)&localValue, 0, sizeof(__Block_byref_localValue_0), 0};
void(*localVariableBlock)(void) = ((void (*)())&__BlockTypeViewController___blockTest_block_impl_3((void *)__BlockTypeViewController___blockTest_block_func_3, &__BlockTypeViewController___blockTest_block_desc_3_DATA, (__Block_byref_localValue_0 *)&localValue, 570425344));
((void (*)(__block_impl *))((__block_impl *)localVariableBlock)->FuncPtr)((__block_impl *)localVariableBlock);
}
複製代碼
__block_impl_
結構體,能夠看到這裏把訪問的局部變量的值拷貝到結構體中了,在函數調用中,是直接訪問結構體中對應的值。所以,這種方式沒法修改外部局部變量的值!struct __BlockTypeViewController___blockTest_block_impl_0 {
struct __block_impl impl;
struct __BlockTypeViewController___blockTest_block_desc_0* Desc;
NSInteger readonlyValue; //拷貝的值
__BlockTypeViewController___blockTest_block_impl_0(void *fp, struct __BlockTypeViewController___blockTest_block_desc_0 *desc, NSInteger _readonlyValue, int flags=0) : readonlyValue(_readonlyValue) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __BlockTypeViewController___blockTest_block_func_0(struct __BlockTypeViewController___blockTest_block_impl_0 *__cself) {
NSInteger readonlyValue = __cself->readonlyValue; // bound by copy
....
}
複製代碼
__block_impl_
結構體,能夠看到這裏將BlockTypeViewController
保存到結構體中,函數調用修改爲員變量時,直接經過結構體中保存的self
引用來修改value
變量。struct __BlockTypeViewController___blockTest_block_impl_1 {
struct __block_impl impl;
struct __BlockTypeViewController___blockTest_block_desc_1* Desc;
BlockTypeViewController *self;
__BlockTypeViewController___blockTest_block_impl_1(void *fp, struct __BlockTypeViewController___blockTest_block_desc_1 *desc, BlockTypeViewController *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __BlockTypeViewController___blockTest_block_func_1(struct __BlockTypeViewController___blockTest_block_impl_1 *__cself) {
BlockTypeViewController *self = __cself->self; // bound by copy
((void (*)(id, SEL, NSInteger))(void *)objc_msgSend)((id)self, sel_registerName("setValue:"), (NSInteger)1);
....
}
複製代碼
__block_impl_
結構體,能夠看到結構體保存了globalValue
的引用,函數修改時,直接修改其引用的值。struct __BlockTypeViewController___blockTest_block_impl_2 {
struct __block_impl impl;
struct __BlockTypeViewController___blockTest_block_desc_2* Desc;
NSInteger *globalValue;
__BlockTypeViewController___blockTest_block_impl_2(void *fp, struct __BlockTypeViewController___blockTest_block_desc_2 *desc, NSInteger *_globalValue, int flags=0) : globalValue(_globalValue) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __BlockTypeViewController___blockTest_block_func_2(struct __BlockTypeViewController___blockTest_block_impl_2 *__cself) {
NSInteger *globalValue = __cself->globalValue; // bound by copy
(*globalValue) = 1;
...
}
複製代碼
__block
修飾符後,會對變量進行引用,並建立一個__Block_byref
結構體。__attribute__((__blocks__(byref))) __Block_byref_localValue_0 localValue = {(void*)0,(__Block_byref_localValue_0 *)&localValue, 0, sizeof(__Block_byref_localValue_0), 0};
struct __Block_byref_localValue_0 {
void *__isa;
__Block_byref_localValue_0 *__forwarding;
int __flags;
int __size;
NSInteger localValue;
};
複製代碼
而後將__Block_byref
對象做爲引用傳入到__block_impl
結構體中,並在函數修改值時,經過__block_impl
獲取到__Block_byref
的引用,再經過__Block_byref
獲取到局部變量的引用,從而達到修改局部變量值的目的。
struct __BlockTypeViewController___blockTest_block_impl_3 {
struct __block_impl impl;
struct __BlockTypeViewController___blockTest_block_desc_3* Desc;
__Block_byref_localValue_0 *localValue; // by ref
__BlockTypeViewController___blockTest_block_impl_3(void *fp, struct __BlockTypeViewController___blockTest_block_desc_3 *desc, __Block_byref_localValue_0 *_localValue, int flags=0) : localValue(_localValue->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __BlockTypeViewController___blockTest_block_func_3(struct __BlockTypeViewController___blockTest_block_impl_3 *__cself) {
__Block_byref_localValue_0 *localValue = __cself->localValue; // bound by ref
(localValue->__forwarding->localValue) = 1;
...
}
複製代碼
總結:全局變量會保存其引用,成員變量會保存
self
指針,從而達到直接修改變量的目的;而局部變量,若未使用__block
修飾,則直接將其值拷貝過去,所以block
內的修改將沒法影響到外部的變量。使用__block
修飾後,將會保存變量的引用。
Block
的Copy
和Release
上面談及到ACR下,對block賦值時,會對block進行
copy
操做,將其從棧中拷貝到堆中。接下來,咱們將經過源碼分析block的拷貝和釋放操做。
_Block_copy
:該操做主要是將棧block拷貝到堆中,稱爲堆block。enum {
//引用計數
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
//堆block標識
BLOCK_NEEDS_FREE = (1 << 24), // runtime
//Block_layout中是否包含Block_descriptor_2的copy
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
//全局block標識
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
//Block_descriptor_3中signature和layout對應標識
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg; //1.
if (aBlock->flags & BLOCK_NEEDS_FREE) { //2.
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) { //3.
return aBlock;
}
else {
// Its a stack block. Make a copy.
//4.
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// 5.
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// 6.
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
//7.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
複製代碼
Block_layout
類型,咱們知道Block
的本質其實就是Block_layout
結構體。BLOCK_NEEDS_FREE
標識判斷block是否在堆中,如果,則直接增長其引用計數便可,不須要再進行拷貝操做。static int32_t latching_incr_int(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) { //若超過引用計數的最大值,則直接返回最大值,避免值溢出
return BLOCK_REFCOUNT_MASK;
}
if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) { //引用計數增長2,block的每次增長是以2位單位
return old_value+2;
}
}
}
複製代碼
根據BLOCK_IS_GLOBAL
標識判斷block是否爲全局block,如果,則直接返回。
首先使用malloc
給新的block分配內存空間,而後再經過memmove
方法將block信息拷貝到新的block中。
這裏先將block的引用計數設置爲0,而後設置標識BLOCK_NEEDS_FREE
,並將引用計數設置爲2。
首先經過_Block_descriptor_2
方法獲取到Block_layout
中的Block_descriptor_2
對象,而後調用其copy
方法。這裏的做用主要是用於使用__block
修飾的變量,會對局部變量進行copy
操做,因此當對block進行copy
時,同時也要對__block
修飾的局部變量進行copy
。
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->copy)(result, aBlock); // do fixup
}
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock) {
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
複製代碼
_NSConcreteMallocBlock
。_Block_release
:主要針對__NSMallocBlock__
類型的blockvoid _Block_release(const void *arg) {
//1.
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
//2.
if (aBlock->flags & BLOCK_IS_GLOBAL) return;
if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
//3.
if (latching_decr_int_should_deallocate(&aBlock->flags)) {
//4.
_Block_call_dispose_helper(aBlock);
//5.
_Block_destructInstance(aBlock);
free(aBlock);
}
}
複製代碼
Block_layout
類型static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) { //引用計數爲最大值,則直接返回
return false; // latched high
}
if ((old_value & BLOCK_REFCOUNT_MASK) == 0) { //引用計數爲0,也直接返回
return false; // underflow, latch low
}
int32_t new_value = old_value - 2;
bool result = false;
if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) { //引用計數爲2的話
new_value = old_value - 1; //設置爲1,代表正在釋放
result = true; //返回true,須要對block進行釋放操做
}
if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) { //更新block的flag標識
return result;
}
}
}
複製代碼
_block
修飾的局部變量進行釋放static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->dispose)(aBlock);
}
複製代碼
_Block_destructInstance
默認是空操做的,最後調用free
方法釋放內存_Block_object_assign
:該方法主要用於block內對外部變量的處理,針對不一樣狀況下,會對外部變量進行不一樣的處理。咱們能夠先看一個例子:- (void)blockAssignTest {
NSString *str = @"str";
void(^block1)(void) = ^{
NSLog(@"%@", str);
};
block1();
__block NSNumber *value = @1;
void(^block2)(void) = ^{
NSLog(@"%@", value);
};
block2();
}
複製代碼
使用clang插件改寫爲c++文件後:
static void _I_BlockTypeViewController_blockAssignTest(BlockTypeViewController * self, SEL _cmd) {
NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_5w_hqdc9zg163j32btftjdkh1kw0000gp_T_BlockTypeViewController_3fbb00_mi_0;
void(*block1)(void) = ((void (*)())&__BlockTypeViewController__blockAssignTest_block_impl_0((void *)__BlockTypeViewController__blockAssignTest_block_func_0, &__BlockTypeViewController__blockAssignTest_block_desc_0_DATA, str, 570425344));
((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
__attribute__((__blocks__(byref))) __Block_byref_value_0 value = {(void*)0,(__Block_byref_value_0 *)&value, 33554432, sizeof(__Block_byref_value_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1)};
void(*block2)(void) = ((void (*)())&__BlockTypeViewController__blockAssignTest_block_impl_1((void *)__BlockTypeViewController__blockAssignTest_block_func_1, &__BlockTypeViewController__blockAssignTest_block_desc_1_DATA, (__Block_byref_value_0 *)&value, 570425344));
((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
}
複製代碼
能夠看到使用了__block
修飾符的變量會建立一個__Block_byref_value_0
來存儲變量,這個在講解__block
的時候也說起到了。咱們先分別對比看下二者的__BlockTypeViewController__blockAssignTest_block_desc_0_DATA
和__BlockTypeViewController__blockAssignTest_block_desc_1_DATA
static struct __BlockTypeViewController__blockAssignTest_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __BlockTypeViewController__blockAssignTest_block_impl_0*, struct __BlockTypeViewController__blockAssignTest_block_impl_0*);
void (*dispose)(struct __BlockTypeViewController__blockAssignTest_block_impl_0*);
} __BlockTypeViewController__blockAssignTest_block_desc_0_DATA = { 0, sizeof(struct __BlockTypeViewController__blockAssignTest_block_impl_0), __BlockTypeViewController__blockAssignTest_block_copy_0, __BlockTypeViewController__blockAssignTest_block_dispose_0};
static struct __BlockTypeViewController__blockAssignTest_block_desc_1 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __BlockTypeViewController__blockAssignTest_block_impl_1*, struct __BlockTypeViewController__blockAssignTest_block_impl_1*);
void (*dispose)(struct __BlockTypeViewController__blockAssignTest_block_impl_1*);
} __BlockTypeViewController__blockAssignTest_block_desc_1_DATA = { 0, sizeof(struct __BlockTypeViewController__blockAssignTest_block_impl_1), __BlockTypeViewController__blockAssignTest_block_copy_1, __BlockTypeViewController__blockAssignTest_block_dispose_1};
複製代碼
二者都包含了copy
和dispose
函數指針,這裏先看看copy
函數指針對應的函數實現:
static void __BlockTypeViewController__blockAssignTest_block_copy_0(struct __BlockTypeViewController__blockAssignTest_block_impl_0*dst, struct __BlockTypeViewController__blockAssignTest_block_impl_0*src) {
_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __BlockTypeViewController__blockAssignTest_block_copy_1(struct __BlockTypeViewController__blockAssignTest_block_impl_1*dst, struct __BlockTypeViewController__blockAssignTest_block_impl_1*src) {
_Block_object_assign((void*)&dst->value, (void*)src->value, 8/*BLOCK_FIELD_IS_BYREF*/);
}
複製代碼
能夠看到二者都調用了_Block_object_assign
方法,但二者傳入了一個大小不一樣的flag
。那麼接下來咱們繼續看_Block_object_assign
對這二者有什麼不一樣的處理。
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
/******* id object = ...; [^{ object; } copy]; ********/
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/******* void (^object)(void) = ...; [^{ object; } copy]; ********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/******* // copy the onstack __block container to the heap // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __block ... x; __weak __block ... x; [^{ x; } copy]; ********/
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/******* // copy the actual field held in the __block container // Note this is MRC unretained __block only. // ARC retained __block is handled by the copy helper directly. __block id object; __block void (^object)(void); [^{ object; } copy]; ********/
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/******* // copy the actual field held in the __block container // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __weak __block id object; __weak __block void (^object)(void); [^{ object; } copy]; ********/
*dest = object;
break;
default:
break;
}
}
複製代碼
如上所示,_Block_object_assign
會根據傳入的flag
來作不一樣的處理
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
複製代碼
這裏主要深挖下BLOCK_FIELD_IS_BYREF
的狀況,即便用了__block
修飾符的狀況,會調用_Block_byref_copy
方法對變量進行拷貝。
static struct Block_byref *_Block_byref_copy(const void *arg) {
//1.
struct Block_byref *src = (struct Block_byref *)arg;
//2.
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
//3.
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
//4.
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
//5.
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
(*src2->byref_keep)(copy, src);
}
else {
//6.
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// 7.
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
複製代碼
__block
修飾的變量會被封裝成一個__Block_byref_value_0
結構體,而這個結構體和Block_byref
是一致的。因此這裏將參數強制轉換爲Block_byref
結構體。struct __Block_byref_value_0 {
void *__isa;
__Block_byref_value_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSNumber *value;
};
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
複製代碼
其中Block_byref_2
和Block_byref_3
會根據不一樣狀況,編譯器將其添加到Block_byref
中,這一點跟Block_layout
是一致的。
malloc
方法將變量拷貝到堆中,並將設置相關信息,這裏要注意flags
標識的設置copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
這裏先設置其爲BLOCK_BYREF_NEEDS_FREE
,代表存在堆中,而後將其引用計數設置爲4,其中調用者和棧各自持有。- (void(^)(void))blockTest1 {
__block NSNumber *value = @1;
void(^block)(void) = ^{
NSLog(@"%@", value);
};
return block;
}
- (void)blockTest2 {
void(^blk)(void) = [self blockTest1];
blk();
}
複製代碼
如上,對於blockTest1
方法中__block
修飾的value
變量,引用計數分別由blockTest1
所處棧和blockTest2
調用方持有。
Block_byref
中包含Block_byref_2
,則須要取出Block_byref_2
,而後分別賦值給copy
對象Block_byref
中包含Block_byref_3
,則須要取出Block_byref_3
,而後賦值layout
指針Block_byref
中不包含Block_byref_2
,則直接使用memmove
方法拷貝。flags
中含有BLOCK_BYREF_NEEDS_FREE
,代表已經存在堆中了,則只須要增長其引用計數便可。_Block_object_dispose
:對block持有的外部變量的釋放操做,與_Block_object_assign
相反。能夠回到上個例子的dispose
函數static void __BlockTypeViewController__blockAssignTest_block_dispose_0(struct __BlockTypeViewController__blockAssignTest_block_impl_0*src) {
_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __BlockTypeViewController__blockAssignTest_block_dispose_1(struct __BlockTypeViewController__blockAssignTest_block_impl_1*src) {
_Block_object_dispose((void*)src->value, 8/*BLOCK_FIELD_IS_BYREF*/);
}
複製代碼
如上所示,都調用了_Block_object_dispose
方法對變量進行處理:
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
複製代碼
這裏主要對BLOCK_FIELD_IS_BYREF
的狀況進行分析_Block_byref_release
方法:
static void _Block_byref_release(const void *arg) {
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
byref = byref->forwarding;
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);
}
free(byref);
}
}
}
複製代碼
這裏首先根據flags
標識判斷對象是否在堆中,若在堆中,而後再根據對象的引用計數判斷是否進行銷燬,若須要銷燬,則判斷Block_byref
對象是否包含dispose
指針,若包含,則調用該函數指針銷燬,最後調用free
方法銷燬Block_byref
。
Objective-C對比起Swift語法會顯得更加繁瑣些,對於一些連貫性的方法操做,使用普通的方法調用方式,可能會顯得很臃腫。這時候就能夠考慮使用
Block
來實現OC的鏈式調用。
以一個簡易計算器的實現爲例,一開始咱們可能會這樣實現:
@interface Calculator : NSObject
@property (nonatomic, assign, readonly) NSInteger result;
- (instancetype)initWithValue:(NSInteger)value;
- (void)add:(NSInteger)value;
- (void)sub:(NSInteger)value;
- (void)multiply:(NSInteger)value;
- (void)divide:(NSInteger)value;
@end
@interface Calculator ()
@property (nonatomic, assign, readwrite) NSInteger result;
@end
@implementation Calculator
- (instancetype)initWithValue:(NSInteger)value {
if (self = [super init]) {
self.result = value;
}
return self;
}
- (void)add:(NSInteger)value {
self.result += value;
}
- (void)sub:(NSInteger)value {
self.result -= value;
}
- (void)multiply:(NSInteger)value {
self.result *= value;
}
- (void)divide:(NSInteger)value {
if (value == 0) {
return;
}
self.result /= value;
}
@end
複製代碼
調用方式:
// (2*3+4-8)/2
Calculator *cal = [[Calculator alloc] initWithValue:2];
[cal multiply:3];
[cal add:4];
[cal sub:8];
[cal divide:2];
複製代碼
明顯這樣的方式看起來邏輯很是不連貫,若是改爲使用鏈式調用呢?
- (Calculator *(^)(NSInteger))add;
- (Calculator *(^)(NSInteger))sub;
- (Calculator *(^)(NSInteger))multiply;
- (Calculator *(^)(NSInteger))divide;
- (Calculator * _Nonnull (^)(NSInteger))add {
return ^(NSInteger value) {
self.result += value;
return self;
};
}
- (Calculator * _Nonnull (^)(NSInteger))sub {
return ^(NSInteger value) {
self.result -= value;
return self;
};
}
- (Calculator * _Nonnull (^)(NSInteger))multiply {
return ^(NSInteger value) {
self.result *= value;
return self;
};
}
- (Calculator * _Nonnull (^)(NSInteger))divide {
return ^(NSInteger value){
self.result /= value;
return self;
};
}
//調用方式:
Calculator *cal = [[Calculator alloc] initWithValue:2];
cal.multiply(3).add(4).sub(8).divide(2);
複製代碼
如上所示,使用鏈式調用的方式,明顯更能使代碼更簡潔,更連貫。而實現這一方式的本質是返回一個返回值爲自身的Block對象。
固然,其實在平時使用的一些第三方庫裏,也常常能見到鏈式調用的影子,好比Masonry
:
__weak typeof(self) weakSelf = self;
[self.blockView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(50).height.mas_equalTo(100);
make.left.mas_equalTo(weakSelf.view.mas_left).offset(20).top.mas_equalTo(weakSelf.view.mas_top).offset(100);
}];
複製代碼
Block
咱們知道
Block
的格式爲返回類型(^名稱)(參數類型),一般咱們定義的時候,通常都是會定義固定個數的參數,好比void(^blk1)(NSString*)
或void(^blk2)(NSString*, NSNumber*)
,但有些場景下,爲了實現Block
的通用性,會考慮使用不定參數Block
。其格式爲void(^blk)()
,即參數列表設置爲空。
- (void)variableBlockTest {
typedef void(^CommonBlock)();
void(^nonParameterBlock)(void) = ^{
NSLog(@"no parameter");
};
void(^oneParameterBlock)(NSString *) = ^(NSString *param){
NSLog(@"parameter: %@", param);
};
void(^twoParameterBlock)(NSString *, NSNumber *) = ^(NSString *param1, NSNumber *param2) {
NSLog(@"parameter: %@, %@", param1, param2);
};
CommonBlock blk1 = nonParameterBlock;
CommonBlock blk2 = oneParameterBlock;
CommonBlock blk3 = twoParameterBlock;
blk1();
blk2(@"str");
blk3(@"str", @2);
}
複製代碼
如上所示,咱們能夠對不定參數CommonBlock
傳入任意參數調用,但前提是須要保證CommonBlock
的內部結構參數個數與傳入參數個數是一致的,不然會出錯,好比blk1(@"str")
這樣調用是不容許的,由於blk1
本質爲nonParameterBlock
,參數個數爲0。這裏咱們只能經過名稱來肯定CommonBlock
的具體參數個數,但有些狀況下,就沒法這麼肯定了。
- (void)doSomethingWithCommonBlock:(CommonBlock)blk {
//沒法肯定blk參數個數
}
複製代碼
固然你也能夠增長一個參數,再調用的時候將blk
的參數個數傳遞過去:
[self doSomethingWithCommonBlock:nonParameterBlock withArgumentsNum:0];
[self doSomethingWithCommonBlock:oneParameterBlock withArgumentsNum:1];
[self doSomethingWithCommonBlock:twoParameterBlock withArgumentsNum:2];
- (void)doSomethingWithCommonBlock:(CommonBlock)blk withArgumentsNum:(NSInteger)num{
}
複製代碼
這樣的處理方式顯然會比較」難看「,有沒什麼更優雅的處理方式呢?其實,根據上面的探究,咱們知道Block
的本質就是一個Block_layout
結構體。在編譯過程當中,clang插件也會將Block
轉換爲對應的結構體,具體轉換規則,能夠看這裏
struct Block_literal_1 {
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 {
unsigned long int reserved; // NULL
unsigned long int size; // sizeof(struct Block_literal_1)
// optional helper functions
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
void (*dispose_helper)(void *src); // IFF (1<<25)
// required ABI.2010.3.16
const char *signature; // IFF (1<<30)
} *descriptor;
// imported variables
};
複製代碼
咱們能夠看到Block_literal_1
中有個signature
變量,即函數簽名,若是能獲取到該變量,則能夠經過NSMethodSignature
獲取到對應的numberOfArguments
,即參數個數。這裏將經過對PromiseKit獲取Block
函數簽名的方式來分析。
struct PMKBlockLiteral {
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
int flags;
int reserved;
void (*invoke)(void *, ...);
struct block_descriptor {
unsigned long int reserved; // NULL
unsigned long int size; // sizeof(struct Block_literal_1)
// optional helper functions
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
void (*dispose_helper)(void *src); // IFF (1<<25)
// required ABI.2010.3.16
const char *signature; // IFF (1<<30)
} *descriptor;
// imported variables
};
typedef NS_OPTIONS(NSUInteger, PMKBlockDescriptionFlags) {
PMKBlockDescriptionFlagsHasCopyDispose = (1 << 25),
PMKBlockDescriptionFlagsHasCtor = (1 << 26), // helpers have C++ code
PMKBlockDescriptionFlagsIsGlobal = (1 << 28),
PMKBlockDescriptionFlagsHasStret = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
PMKBlockDescriptionFlagsHasSignature = (1 << 30)
};
static NSMethodSignature *NSMethodSignatureForBlock(id block) {
if (!block)
return nil;
//1.
struct PMKBlockLiteral *blockRef = (__bridge struct PMKBlockLiteral *)block;
//2.
PMKBlockDescriptionFlags flags = (PMKBlockDescriptionFlags)blockRef->flags;
//3
if (flags & PMKBlockDescriptionFlagsHasSignature) {
void *signatureLocation = blockRef->descriptor;
signatureLocation += sizeof(unsigned long int);
signatureLocation += sizeof(unsigned long int);
//4.
if (flags & PMKBlockDescriptionFlagsHasCopyDispose) {
signatureLocation += sizeof(void(*)(void *dst, void *src));
signatureLocation += sizeof(void (*)(void *src));
}
//5.
const char *signature = (*(const char **)signatureLocation);
return [NSMethodSignature signatureWithObjCTypes:signature];
}
return 0;
}
複製代碼
PMKBlockLiteral
結構體,以便獲取到對應的信息;flags
標識,用於判斷是否包含函數簽名;block_descriptor
的signature
;copy_helper
和dispose_helper
指針,因此也要經過flags
標識來判斷,從而肯定是否要移動指針位置;signature
轉換爲NSMethodSignature
。- (void)doSomethingWithCommonBlock:(CommonBlock)blk {
NSMethodSignature *signature = NSMethodSignatureForBlock(blk);
if (!signature) {
return;
}
NSInteger arguments = signature.numberOfArguments-1;
if (arguments == 0) {
blk();
} else if (arguments == 1) {
blk(@"str");
} else if (arguments == 2) {
blk(@"str", @2);
}
}
複製代碼
這樣咱們就不用額外傳入block對應的參數個數了,這裏要注意一點的是block的參數個數是signature.numberOfArguments-1
。由於NSMethodSignature
的numberOfArguments
是包含self
和_cmd
這兩個參數的,因此對應方法真正的參數個數應該減2處理,而block不含_cmd
,因此減1便可。
不定參數Block在實際應用中還有不少場景,比較典型的就是上面介紹的PromiseKit,利用不定參數block來實現then
操做的通用性,固然不定參數Block也有一個比較明顯的缺點:沒法提供代碼提示,須要本身手動去寫參數。對於這個缺點,其實也能夠經過自定義代碼塊處理。
筆者學習完PromiseKit和promises後,針對二者優缺點,也造了一個關於promise
概念的輪子JPromise。