Block In OC
block 分为以下三种:
-
_NSConcreteStackBlock
:栈block,引用了自动变量的block; -
_NSConcreteMallocBlock
:堆block,栈block执行copy得到的block; -
_NSConcreteGlobalBlock
:全局block,没有引用自动变量的block。
捕获规则:
- 捕获动作发生在创建block时;
- 对于全局变量(静态/非静态):在作用域中,不需要捕获;
- 对于静态变量(非全局):引用拷贝;
- 对于自动变量:值拷贝;
- 对于block变量(
__block
只能修饰自动变量):将变量包装成为结构体,引用拷贝该结构体。
示例代码:
typedef void(^Block)(void);
static int a = 1;
@implementation TestBlock
- (void)test {
static int b = 1;
int c = 1;
__block int d = 1;
NSObject *e = [NSObject new];
__weak NSObject *f = e;
__block NSObject *g = [NSObject new];
Block block = ^{
a = 2;
b = 2;
//c = 2; // error: Variable is not assignable
printf("c = %d", c);
d = 2;
printf("e -> %p", e);
printf("f -> %p", f);
printf("f -> %p", g);
};
block();
}
@end
等效于下面的代码:
typedef void(^Block)(void);
static int a = 1;
struct _struct_d { int d; };
struct _struct_g { NSObject *g; };
void _impl_block(int *b, const int c, struct _struct_d *d,
__strong NSObject *e, __weak NSObject *f,
struct _struct_g *g) {
a = 2; // a为全局变量,可以直接访问
*b = 2; // b为静态变量,引用拷贝
//c = 2; //error
printf("c = %d", c); // c为自动变量,值拷贝,并且拷贝后的变量为常量
(*d).d = 2; // d为 __block 修饰的自动变量,将d包装为 struct _struct_d *,然后拷贝该指针
printf("e -> %p", e);
printf("f -> %p", f);
printf("f -> %p", g);
}
struct _struct_block {
// 引用计数器。初始化之后引用计数为1;每增加一个引用时计数器+1;每减少一个引用时计数器-1;计数器为0时触发 free。
int _retainCount;
int *b;
const int c;
struct _struct_d *d;
__strong NSObject *e;
__weak NSObject *f;
struct _struct_g *g;
void (* _impl_block)(int *, const int,
struct _struct_d *, __strong NSObject *,
__weak NSObject *, struct _struct_g *);
};
@implementation TestBlock
- (void)test {
static int b = 1;
int c = 1;
// 将d转为指向堆区内存的指针(该堆区内存的管理未给出)
struct _struct_d *d = (struct _struct_d *)malloc(sizeof(struct _struct_d));
(*d).d = 1;
__strong NSObject *e = [NSObject new];
__weak NSObject *f = e;
// 将g转为指向堆区内存的指针(该堆区内存的管理未给出)
struct _struct_g *g = (struct _struct_g *)malloc(sizeof(struct _struct_g *));
(*g).g = [NSObject new];
struct _struct_block block = {
1, // 初始化 block 后,引用计数为 1
&b, // 静态变量不会被释放,引用拷贝即可
c, // 普通变量,值拷贝
d, // 指向堆区内存的指针,值拷贝该指针即可
e, // 指针,值拷贝;由于是 __strong 类型,对象的引用计数会增加
f, // 指针,值拷贝;由于是 __weak 类型,对象的引用计数不变
g, // 指针,值拷贝;拷贝 struct _struct_g 的指针不会影响到 NSObject 对象的引用计数
_impl_block // 目标函数
}; // 拷贝变量,发生在创建 block 时;由于引用了自动变量,所以该 block 是栈 block。
// 执行 block,不需要从上下文中获取变量
block._impl_block(block.b, block.c, block.d, block.e, block.f, block.g);
block._retainCount --;
}
@end
总结:
- 栈block的copy得到堆block,堆block和全局block的copy返回自身;
- 在ARC中,赋值操作会触发栈block的copy,所以在ARC中大多数block是堆block;
- 在MRC中block捕获的OC对象指针会触发该对象的retain操作,防止出现这个现象的方法是:用
__block
修饰OC对象指针,将指针包装为结构体,copy操作不会复制该结构体; - 在ARC中block捕获OC对象指针时,内部的指针会继承该指针的(
__strong
/__unsafe_unretained
/__weak
)修饰符,所以防止block强引用捕获的OC对象的方法是:用__weak
获取一个弱引用OC对象的指针,在block使用__strong
获取一个强引用OC对象的指针,防止block执行过程中OC对象被释放。
将OC编译为C++的指令:
clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations block_arc.m -o block_arc.cpp
Closure In Swift
捕获规则:
- 捕获动作发生在创建closure时,捕获动作分为直接捕获和列表捕获;
- 直接捕获:引用拷贝,类似OC中
__block int
或__block NSObject *
的做法; - 列表捕获非class:值拷贝,类似OC中
int
的做法; - 列表捕获class,拷贝对象的指针。根据使用的前缀不同,分为以下情况:
- 没有前缀:得到对象的
__strong
指针,对象引用计数+1; -
unowned
前缀:得到对象的__unsafe_unretained
指针,对象引用计数不变; -
weak
前缀:得到对象的__weak
指针,对象引用计数不变。
- 没有前缀:得到对象的
函数转变为 closure:
函数被变量引用时,会自动转变为闭包,并且执行捕获动作。
如果函数是实例的成员函数,除常规捕获外,还会捕获该实例。
这是容易引发循环引用的原因之一。
网友评论