根底
- Block是⼀个自包括的(捕获了上下⽂的常量或许是变量的)函数代码块,能够在代码中被传递和运用。
- 大局和嵌套函数实际上也是特别的闭包,闭包采用如下三种形式之一:
- 大局函数是一个有姓名但不会捕获任何值的闭包
- 嵌套函数是一个有姓名并能够捕获其封闭函数域内值的闭包
- 闭包表达式是一个运用轻量级语法所写的能够捕获其上下文中变量或常量值的匿名闭包
OC-Block
分类
-
NSGlobalBlock
- 坐落大局区
- 在Block内部不运用外部变量,或许只运用静态变量和大局变量
-
NSMallocBlock
- 坐落堆区
- 被强持有
- 在Block内部运用局部变量或OC特点,能够赋值给强引证/copy润饰的变量
-
NSStackBlock
- 坐落栈区
- 没有被强持有
- 在Block内部运用局部变量或OC特点,不能赋值给强引证/copy润饰的变量
如下简略demo code所示
int a = 10; // 局部变量
void(^Global)(void) = ^{
NSLog(@"Global");
};
void(^Malloc)(void) = ^{
NSLog(@"Malloc,%d",a);
};
void(^__weak Stack)(void) = ^{
NSLog(@"Stack,%d",a);
};
NSLog(@"%@",Global); // <__NSGlobalBlock__: 0x101aa80b0>
NSLog(@"%@",Malloc); // <__NSMallocBlock__: 0x600003187900>
NSLog(@"%@",Stack); // <__NSStackBlock__: 0x7ff7b12c22f0>
下面要点介绍堆Block。
NSMallocBlock
Block复制到堆Block的时机:
- 手动copy
- Block作为返回值
- 被强引证/copy润饰
- 系统API包括using Block
所以总结一下堆Block判别依据:
- Block内部有没有运用外部变量
- 运用的变量类型?局部变量/OC特点/大局变量/静态变量
- 有没有被强引证/copy润饰
源码探求
咱们创立一个捕获了局部变量的block
#import <Foundation/Foundation.h>
void test() {
int a = 10;
void(^Malloc)(void) = ^{
NSLog(@"%d",a);
};
}
执行clang -rewrite-objc main.m -o main.cpp
命令,检查main.cpp文件能够看到Malloc闭包的结构如下。
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
// 内部存储了变量a
int a;
/// 初始化函数。包括三个参数
// - Parameters:
/// - fp: 函数指针
/// - desc: 描绘
/// - _a: flag
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 创立Malloc闭包,传入参数如下
// fp: (void *)__test_block_func_0
// desc: &__test_block_desc_0_DATA
// _a: 变量a的值(值复制)
void(*Malloc)(void) = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a));
// __test_block_func_0完成如下
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog();
}
翻开llvm能够看到,该block原本是在栈上,调用了objc_retainBlock
办法,而在该办法中实际调用了_Block_copy
办法。
在Block.h的源码中能够找到_Block_copy
办法,其官方注释是“创立一个根据堆的Block副本,或许简略地增加一个对现有Block的引证。”,从而将这个栈block复制到了堆上,下面咱们根据该办法的源码来探求一下堆Block的原理。(只截取要点代码)
void *_Block_copy(const void *arg) {
return _Block_copy_internal(arg, true);
}
static void *_Block_copy_internal(const void *arg, const bool wantsOne) {
struct Block_layout *aBlock;
// 类型强转为Block_layout
aBlock = (struct Block_layout *)arg;
// Its a stack block. Make a copy.
// 分配内存
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// isa重新标记为Malloc Block
result->isa = _NSConcreteMallocBlock;
_Block_call_copy_helper(result, aBlock);
return result;
}
Block底层结构为Block_layout
struct Block_layout {
void *isa; // isa指针
volatile int32_t flags; // contains ref count
int32_t reserved; // 保留位
void (*invoke)(void *, ...); // call out funtion
struct Block_descriptor_1 *descriptor;
};
总结:
Block在运行时才会被copy,在堆上拓荒内存空间。
循环引证
解决方案
-
__weak
+__strong
思路: 在block里短暂持有self的生命周期。(
weak
自动置空)self.name = @"YK"; __weak typeof(self) weakSelf = self; self.block = ^{ __strong typeof(self) strongSelf = weakSelf; strongSelf.callFunc(); };
-
__block
思路: 值复制。(手动置空)
咱们有如下代码,生成cpp文件看一下
#import <Foundation/Foundation.h>
void test() {
__block int a = 10;
void(^Malloc)(void) = ^{
a++;
NSLog(@"%d",a);
};
Malloc();
}
// 能够看到传入的第三个参数,是__Block_byref_a_0结构体类型的a变量地址,而不是上面讲过的直接存储int类型
void(*Malloc)(void) =
((void (*)())&__test_block_impl_0((void *)__test_block_func_0,
&__test_block_desc_0_DATA,
(__Block_byref_a_0 *)&a,
570425344));
// __test_block_impl_0结构体中存储的变量也是__Block_byref_a_0类型
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 初始化__Block_byref_a_0如下
__attribute__((__blocks__(byref))) __Block_byref_a_0 a =
{(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
10};
// __Block_byref_a_0结构体
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding; // 指针指向原始值
int __flags;
int __size;
int a; // 值复制存储
};
总结 __block
原理
- 创立
__Block_byref_a_0
结构体 - 传给block指针地址
- block內修正的是与原始值同一片的内存空间
留意点
根据上述分析咱们能够得出结论,假如在OC的block中捕获了没有加__block
的外部变量,在编译时就会将变量值传入(值复制),假如捕获了加__block
的外部变量,则会获取到变量指针对应的内存空间的地址。代码验证如下
int a = 1;
__block int b = 2;
void(^Malloc)(void) = ^{
NSLog(@"a,%d",a);
NSLog(@"b,%d",b);
};
a = 3;
b = 4;
Malloc();
// 输出成果如下
// a,1
// b,4
Swift-Closure
- Swift 的闭包表达式具有简练的风格,并鼓励在常见场景中进行语法优化,首要优化如下:
- 运用上下文推断参数类型和返回值类型
- 隐式返回单表达式闭包(单表达式闭包能够省掉
return
关键字) - 参数名称缩写,能够用0,0,1表明
- 跟随闭包语法:假如函数的最后一个参数是闭包,则闭包能够写在形参小括号的外面。为了增强函数的可读性。
- Swift 的闭包是一个引证类型,验证如下。咱们知道Swift的引证类型在创立时都会调用
swift_allocObject
办法
// 未调用swift_allocObject
let closure1 = { () -> () in
print("closure1")
}
// 调用swift_allocObject
let a = 10
let closure2 = { () -> () in
print("closure2 \(a)")
}
捕获值
- 在闭包中假如经过
[variable1, variabla2]
的形式捕获外部变量,捕获到的变量为let
类型,即不可变 - 在闭包中假如直接捕获外部变量,获取的是指针,也就是说在闭包内修正变量值的话,原始变量也会被改变。
- 假如捕获的是指针类型(
Class
),不管是否用[],在闭包内对该变量进行修正,都会影响到原始变量
简略验证如下:
var variable = 10
let closure = { () -> () in
variable += 1
print("closure \(variable)")
}
closure() // closure 11
print(variable) // 11
可见直接获取变量的话,会修正到原始值。
假如改成下面这样会编译报错”可变运算符的左侧不可变”
var variable = 10
let closure = { [variable] () -> () in
variable += 1
print("closure \(variable)")
}
closure()
print(variable)
捕获指针类型验证
class YKClass {
var name = "old"
}
let demoS = YKStruct()
let demoC = YKClass()
let closure1 = { [demoC] () -> () in
demoC.name = "new"
print("closure1 \(demoC.name)")
}
closure1() // closure1 new
print(demoC.name) // new
let closure2 = { () -> () in
demoC.name = "new2"
print("closure2 \(demoC.name)")
}
closure2() // closure2 new2
print(demoC.name) // new2