Objective-C当中的Block
Objective-C当中的Block
Blocks是C语言的扩充功能,即带有自动变量(局部变量)的匿名函数。 使用clang
可以通过添加-rewrite-objc
编译选项将含有Block语法的源代码变换为C++的源代码,如用clang -rewrite-objc main.m
会生成一个与main.m
对等的main.cpp
文件。
先看总结:
- 当没有引入外部自动变量时,
Block
是__NSGlobalBlock__
; - 当只引入了非自动变量(如
void *
、变量的类型)时,Block
仍然是__NSGlobalBlock__
; - 当引入了外部自动变量时,
Block
会变成__NSMallocBlock__
; - 用
__weak
修饰之后,Block
变成了__NSStackBlock__
; - 引入静态变量、全局变量或全局静态变量,
Block
是__NSGlobalBlock__
; Block
截获的自动变量值被保存在Block
结构体实例(即Block自身)中,即__main_block_impl_0
结构体中;Block
优先设置在__NSStackBlock__
上,只有当Block
引入的变量的作用域结束时,才会把Block
及__block
变量从栈复制到堆上,同时栈上的Block
与__block
变量被废弃,堆上的不受影响;__NSGlobalBlock__
类型的Block
是存放在全局数据区的,只要Block
不截获自动变量,那Block
结构体实例就会存放在全局数据区,即程序的__DATA__
段中。-
当通过函数返回的
Block
,函数返回时编译器会自动生成复制到堆上的代码objc_retainBlock
,该函数内直接调用了_Block_copy
,而此函数就会将Block
从栈上复制到堆上; Block
结构体类似于基于objc_object
结构体的Ojbective-C类对象的结构体,其中的成员变量isa
在构造函数中是这样isa = &_NSConcreteStackBlock;
被初始化的。blk()
的调用,实际上是调用__main_block_impl_0
里__block_impl
实例impl
的成员FuncPtr
,而FuncPtr
即是构造Block
对象(__main_block_impl_0
)时传入的__main_block_func_0
函数指针;-
__main_block_copy_0
与__main_block_dispose_0
是拷贝和释放赋值在对象类型的结构体成员变量中的对象,在Block
从栈复制到堆时以及堆上的Block
被废弃时会调用这两函数; -
当变量用
__block
修饰变量a
后,在block捕获变量a
后会为变量生成一个叫__Block_byref_a_0
的__block结构体
,用来存放a
变量,并且在__main_block_impl_0
的结构体中引用__Block_byref_a_0
对象的指针作为结构体的成员变量。 - 以下几种情况栈上的
Block
会复制到堆上:- 调用
Block
的copy
实例方法; Block
作为函数返回值返回时;- 将
Block
赋值给附有__strong
修饰的id类型变量或Block
类型成员变量时; - 在方法名中含有
usingBlock
的cocoa
框架方法或CGD的API中传递Block
时;
- 调用
编译源码libclosure
-
把
Base SDK
设置为macOS
; - 从Apple Source code下载源码;
-
当编译源码
libclosure
报<platform/string.h> file not found时,把libplatform
下载解压后,把头文件导入macosx
的sdk
的路径下的/usr/local/include
下,使用如下命令:sudo ditto $PWD/private $(xcrun -sdk macosx -show-sdk-path)/usr/local/include //查看`macosx`的`sdk`的路径:`xcrun -sdk macosx -show-sdk-path`;
-
同理,如果报
<os/assumes.h> file not found
,把Libc
下下来并把其中os
下的头文件拷贝到$(xcrun -sdk macosx -show-sdk-path)/usr/local/include/os/
下。 -
如果报*included file ‘
/Makefiles/CoreOS/Xcode BSD.xcconfig does not exist*,就把[ CoreOSMakefiles](https://luowei.github.io/https://opensource.apple.com/tarballs/CoreOSMakefiles/CoreOSMakefiles-79.tar.gz)下载下来解压后,把里面的`BSD.xcconfig`拷贝到`/Applications/Xcode.app/Contents/Developer`下。 - 当然更好的方式,是在项目target的 Build Settings -> Header Search Paths 里添加,如这样:
$(SRCROOT)/LWCommon //自己创建的文件夹,放缺失的头文件 $(DSTROOT)/usr/include $(DSTROOT)/usr/local/include $(CONFIGURATION_BUILD_DIR)/usr/include $(CONFIGURATION_BUILD_DIR)/usr/local/include /System/Library/Frameworks/System.framework/PrivateHeaders
- 源码编译成功后,建立新的 target 来执行你程序,从而跑进源码,在target上设置target -> objc -> Build Settings -> Enable Hardened Runtime -> NO 即可。
下面用代码来验证这些结论:
#import <Foundation/Foundation.h>
#pragma clang diagnostic push
@interface Hello : NSObject
-(NSString *)uname;
@end
@implementation Hello
-(NSString *)uname{
return @"张三";
}
@end
typedef int(^Block_t) (int);
//1. 当没有引入外部自动变量时,blk是__NSGlobalBlock__
void testBlock1() {
void (^blk)(void) = ^{
NSLog(@"Hello");
};
blk();
NSLog(@"====blk1:%@",blk);
// for(int i=0;i<10;i++){
// Block_t blk_t = ^(int count){return count * i;};
// blk_t(5);
// NSLog(@"====blk_t1:%@",blk_t);
// }
}
//2. 当引入了非自动变量(如void*、变量的类型)时,blk还是__NSGlobalBlock__
void testBlock2() {
//void *hello = (__bridge void *)([[Hello alloc] init]);
Hello *hello = [[Hello alloc] init];
//分配1个32字节的空间
void *p1 = calloc(1, 32);
void *p2 = calloc(1, 16);
void (^blk)(void) = ^{
NSLog(@"hello size:%lu",sizeof(hello)); //这样引入就还是__NSGlobalBlock__
// NSLog(@"hello:%@",hello); //这样引入就变成了__NSMallocBlock__
NSLog(@"p1:%lu p2:%lu",sizeof(p1),sizeof(p2));
// 如果此时调用free释放掉p1,p2,则相当于引入了自动变量
// free(p1);
// free(p2);
};
blk();
NSLog(@"====blk2:%@",blk);
}
//3. 当引入了外部自动变量时,blk会变成__NSMallocBlock__
void testBlock3() {
int a = 10;
void (^blk)(void) = ^{
NSLog(@"a:%i",a);
};
blk();
NSLog(@"====blk3:%@",blk);
}
#pragma clang diagnostic ignored "-Warc-unsafe-retained-assign"
//4.用__weak修饰之后,变成了__NSStackBlock__
void testBlock4() {
int a = 10;
void (__weak ^blk)(void) = ^{
NSLog(@"a:%i",a);
};
blk();
NSLog(@"====blk4:%@",blk);
}
//5.引入静态变量、全局变量或全局静态变量,是__NSGlobalBlock__
int global_var = 1;
static int static_global_var = 2;
void testBlock5() {
//静态变量
static int static_var = 3;
void (^blk)(void) = ^{
global_var *= 3;
static_global_var *= 3;
static_var *= 3;
NSLog(@"global_var:%i",global_var);
NSLog(@"static_global_var:%i",static_global_var);
NSLog(@"static_var:%i",static_var);
};
blk();
NSLog(@"====blk5:%@ static_var:%i",blk,static_var);
}
//6.通过函数返回的block,函数返回时编译器会自动生成复制到堆上的代码 objc_retainBlock,
// 该函数内直接调用了_Block_copy,将Block复制到堆上
Block_t getBlock(int rate){
return ^(int count){ return rate * count; };
}
void testBlock6(){
Block_t blk = getBlock(3);
int count = blk(2);
NSLog(@"====blk6:%@ returnValue:%i",blk,count);
}
//7.block存放到复数,会自动复制到堆上
NSArray* getBlockList(){
int a = 10;
return @[
^{NSLog(@"block list 0:%i",a);},
^{NSLog(@"block list 1:%i",a);},
];
}
void testBlock7(){
NSArray *blockList = getBlockList();
typedef void(^blk_t)(void);
blk_t blk = (blk_t)blockList.firstObject;
blk();
NSLog(@"====blk7:%@",blk);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
testBlock1();
testBlock2();
testBlock3();
testBlock4();
testBlock5();
testBlock6();
testBlock7();
}
return 0;
}
#pragma clang diagnostic pop
输出:
hello_block[56269:5472036] Hello
hello_block[56269:5472036] ====blk1:<__NSGlobalBlock__: 0x100399050>
hello_block[56269:5472036] hello size:8
hello_block[56269:5472036] p1:8 p2:8
hello_block[56269:5472036] ====blk2:<__NSGlobalBlock__: 0x100399070>
hello_block[56269:5472036] a:10
hello_block[56269:5472036] ====blk3:<__NSMallocBlock__: 0x7fa50ec06440>
hello_block[56269:5472036] a:10
hello_block[56269:5472036] ====blk4:<__NSStackBlock__: 0x7ffeef867f98>
hello_block[56269:5472036] global_var:3
hello_block[56269:5472036] static_global_var:6
hello_block[56269:5472036] static_var:9
hello_block[56269:5472036] ====blk5:<__NSGlobalBlock__: 0x1003990b0> static_var:9
hello_block[56269:5472036] ====blk6:<__NSMallocBlock__: 0x7fa50ef04470> returnValue:6
hello_block[56269:5472036] block list 0:10
hello_block[56269:5472036] ====blk7:<__NSMallocBlock__: 0x7fa50ef04790>