ios 添加block 类别_iOS-Block源码分析

古巷悠悠岁月深,青石老街印旧痕

今夜小楼听风雨,不见当年伞下人

前言

Block作为iOS中老生常谈的问题,也是面试中面试官比较喜欢问的 一个问题 ,下面我们通过源码查看block的底层实现原理

什么是Block

Block:将函数及其上下文组装起来的对象

Block本质就是一个对象

创建一个PHJBlock类

@implementation PHJBlock

- (void)test {

int a = 10;

void (^ block)(void) = ^{

NSLog(@"%d", a);

};

block();

}

@end

查看编译后的C++源码

编译: Clang -rewrite-objc PHJBlock.m

static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) {

int a = 10;

void (* block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, a));

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

}

__PHJBlock__test_block_impl_0的内部实现

struct __PHJBlock__test_block_impl_0 {

struct __block_impl impl;

struct __PHJBlock__test_block_desc_0* Desc;

int a;

__PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int _a, int flags=0) : a(_a) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

__block_impl的内部实现

看到isa,证明block的本质就是一个对象

struct __block_impl {

void *isa; // 看到isa,证明block的本质就是一个对象

int Flags;

int Reserved;

void *FuncPtr;

};

Block的三种类型

栈Block

堆Block

全局Block

int a = 10;

// 堆block

void(^block)(void) = ^{

NSLog(@"%d", a);

};

block();

NSLog(@"%@", block);

// 全局block

void(^block1)(void) = ^{

};

block1();

NSLog(@"%@", block1);

// 栈block

NSLog(@"%@", ^{

NSLog(@"%d", a);

});

打印查看block内存地址

Block捕获外部变量

局部变量

基本数据类型:截获其值

对象类型:对于对象类型的局部变量连同所有权修饰符一起截获

静态局部变量

以指针形式

全局变量

不截获

静态全局变量

不截获

Block捕获基本数据类型局部变量

不加修饰词修饰的变量

- (void)test {

int a = 100;

void(^block)(void) = ^{

printf("%d", a);

};

block();

}

编译后的C++代码:捕获外部变量的值

static void __PHJBlock__test_block_func_0(struct __PHJBlock__test_block_impl_0 *__cself) {

// 捕获 a 的值

int a = __cself->a; // bound by copy

printf("%d", a);

}

加修饰词修饰的变量

- (void)test {

__block int a = 100;

void(^block)(void) = ^{

a ++;

printf("%d", a);

};

block();

}

编译后的C++代码:捕获外部变量的的地址

总结:这也就解释了为什么我们在外部变量前加上__block就能在block内部可以修改变量的值

static void __PHJBlock__test_block_func_0(struct __PHJBlock__test_block_impl_0 *__cself) {

// 捕获 a 的地址

__Block_byref_a_0 *a = __cself->a; // bound by ref

(a->__forwarding->a) ++;

printf("%d", (a->__forwarding->a));

}

发现变量a加上__block后变成了一个对象

__forwarding指针

struct __Block_byref_a_0 {

void *__isa;

__Block_byref_a_0 *__forwarding;

int __flags;

int __size;

int a;

};

Block捕获对象类型局部变量

- (void)test {

__unsafe_unretained id obj = nil;

__strong NSObject *obj1 = nil;

void(^block)(void) = ^{

NSLog(@"__unsafe_unretained类型变量:%@", obj);

NSLog(@"__strong类型变量:%@", obj1);

};

block();

}

编译后的C++代码:连同外部变量的修饰词一起捕获

static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) {

// 修饰词一起捕获

__attribute__((objc_ownership(none))) id obj = __null;

__attribute__((objc_ownership(strong))) NSObject *obj1 = __null;

void(*block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, obj, obj1, 570425344));

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

}

总结:这也就解释了为什么我们在外部对象前加上__weak就能在block内部使用的时候可以避免循环引用问题

Block捕获静态局部变量

- (void)test {

static int a = 100;

void(^block)(void) = ^{

NSLog(@"static类型变量a :%d", a);

};

block();

}

编译后的C++代码:捕获变量的地址

&a代表传入的是a变量的地址

static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) {

static int a = 100;

// 下面的&a代表传入的是a变量的地址

void(*block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, &a));

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

}

int *a接收传入的a变量的地址

struct __PHJBlock__test_block_impl_0 {

struct __block_impl impl;

struct __PHJBlock__test_block_desc_0* Desc;

// 捕获a变量的地址

int *a;

__PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

Block不会捕获全局变量

int a = 100;

@implementation PHJBlock

- (void)test {

void(^block)(void) = ^{

NSLog(@"全局变量a :%d", a);

};

block();

}

@end

编译后的C++代码:不会捕获全局变量

struct __PHJBlock__test_block_impl_0 {

struct __block_impl impl;

struct __PHJBlock__test_block_desc_0* Desc;

__PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int flags=0) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

Block不会捕获静态全局变量

static int a = 100;

@implementation PHJBlock

- (void)test {

void(^block)(void) = ^{

NSLog(@"全局变量a :%d", a);

};

block();

}

@end

编译后的C++代码:不会捕获静态全局变量

struct __PHJBlock__test_block_impl_0 {

struct __block_impl impl;

struct __PHJBlock__test_block_desc_0* Desc;

__PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int flags=0) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

一般情况下对截获变量进行赋值操作需要加__block

需要加__block

__block NSMutableDictionary *dicM = nil;

void(^block)(void) = ^{

dicM = [NSMutableDictionary dictionary];

};

block();

不需要加__block

NSMutableDictionary *dicM = nil;

void(^block)(void) = ^{

[dicM setObject:@(1) forKey:@"1"];

};

block();

总结:赋值的时候需要加__block,操作使用的时候不用加

__forwarding指针

栈Block没有进行copy操作

栈__forwarding指针都指向栈中自己的变量

栈Block如果进行了copy操作

栈和堆上的__forwarding指针都指向堆的变量

总结:不论在任何内存位置,都可以顺利访问同一个__block变量

Block的Copy操作

栈block进行copy,得到堆block

堆block进行copy,增加引用计数

全局block进行copy,block不会产生影响

__block 修饰局部对象类型变量的循环引用问题

MRC下,不会产生循环引用

在ARC下,会产生循环引用,引起内存泄漏,解决:在block内部对__block修饰的对象变量进行置nil操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值