ios 添加block 类别_深入理解iOS的block(上)

前言

在文章之前,先抛出如下问题。block的原理是怎样的?本质是什么?

`__block`的作用是什么?有什么使用注意点?

block的属性修饰词为什么是copy?使用block有哪些使用注意?

block一旦没有进行copy操作,就不会在堆上

block在修改NSMutableArray,需不需要添加__block?

如果现在不是很熟悉,希望看完这篇文章,能有个新的认识。

导读

本文主要从如下几个方面讲解blockblock的基本使用

block在内存中的布局

block对变量的捕获分析

MRC和ARC的对比

`__block`的分析

block中内存管理问题

block导致的循环引用问题

什么是blockIn programming languages, a closure is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.

翻译过来表达就是闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称作自由变量)。

一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:1012951431, 分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!希望帮助开发者少走弯路。

block 实际上就是 Objective-C 语言对于闭包的实现。

block的基本使用block本质上也是一个OC对象,它内部也有个isa指针

block是封装了函数调用以及函数调用环境的OC对象

block的底层结构如下图

无参无返回值的定义和使用

//无参无返回值 定义 和使用

void (^MyBlockOne)(void) = ^{

NSLog(@"无参无返回值");

};

// 调用

MyBlockOne();

无参有返回值的定义和使用

// 无参有返回值

int (^MyBlockTwo)(void) = ^{

NSLog(@"无参有返回值");

return 2;

};

// 调用

int res = MyBlockTwo();

有参无返回值的定义和使用

//有参无返回值 定义

void (^MyBlockThree)(int a) = ^(int a){

NSLog(@"有参无返回值 a = %d",a);

};

// 调用

MyBlockThree(10);

有参有返回值的定义和使用

//有参有返回值

int (^MyBlockFour)(int a) = ^(int a){

NSLog(@"有参有返回值 a = %d",a);

return a * 2;

};

MyBlockFour(4);

typedef 定义Block

实际开发中,经常需要把block作为一个属性,我们可以定义一个block

eg:定义一个有参有返回值的block

typedef int (^MyBlock)(int a, int b);

定义属性的时候,如下即可持有这个block

@property (nonatomic,copy) MyBlock myBlockOne;

block实现

self.myBlockOne = ^int(int a, int b) {

return a + b;

};

调用

self.myBlockOne(2, 5);

block 类型和数据结构

block 数据结构分析

生成cpp文件

如下代码

int age = 20;

void (^block)(void) = ^{

NSLog(@"age is %d",age);

};

block();

打开终端,cd到当前目录下

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

生成`main.cpp`

block 结构分析

int age = 20;

// block的定义

void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

// block的调用

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

上面的代码删除掉一些强制转换的代码就就剩下如下所示

int age = 20;

void (*block)(void) = &__main_block_impl_0(

__main_block_func_0,

&__main_block_desc_0_DATA,

age

);

// block的调用

block->FuncPtr(block);

看出block的本质就是一个结构体对象,结构体`__main_block_impl_0`代码如下

struct __main_block_impl_0 {

struct __block_impl impl;

struct __main_block_desc_0* Desc;

int age;

//构造函数(类似于OC中的init方法) _age是外面传入的

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {

//isa指向_NSConcreteStackBlock 说明这个block就是_NSConcreteStackBlock类型的

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

结构体中第一个是`struct __block_impl impl;`

struct __block_impl {

void *isa;

int Flags;

int Reserved;

void *FuncPtr;

};

结构体中第二个是`__main_block_desc_0;`

static struct __main_block_desc_0 {

size_t reserved;

size_t Block_size; // 结构体__main_block_impl_0 占用的内存大小

}

结构体中第三个是`age`

也就是捕获的局部变量 `age`

`__main_block_func_0`

//封装了block执行逻辑的函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

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

NSLog((NSString *)&__NSConstantStringImpl__var_folders_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_7f3f1b_mi_0,age);

}

用一幅图来表示

变量捕获

其实上面的代码我们已经看得出来变量捕获了,这里继续详细分析一下

局部变量auto(自动变量)

我们平时写的局部变量,默认就有 auto(自动变量,离开作用域就销毁)

运行代码

例如下面的代码

int age = 20;

void (^block)(void) = ^{

NSLog(@"age is %d",age);

};

age = 25;

block();

等同于

auto int age = 20;

void (^block)(void) = ^{

NSLog(@"age is %d",age);

};

age = 25;

block();

输出20

分析xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

生成`main.cpp`

如图所示

int age = 20;

void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

age = 25;

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

struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;

NSLog((NSString *)&__NSConstantStringImpl__var_folders_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_d36452_mi_5);

可以知道,直接把age的值 20传到了结构体`__main_block_impl_0`中,后面再修改`age = 25`并不能改变block里面的值

局部变量 static

static修饰的局部变量,不会被销毁

运行代码

eg

static int height = 30;

int age = 20;

void (^block)(void) = ^{

NSLog(@"age is %d height = %d",age,height);

};

age = 25;

height = 35;

block();

执行结果为

age is 20 height = 35

可以看得出来,block外部修改height的值,依然能影响block内部的值

分析xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

生成`main.cpp`

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

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

int *height = __cself->height; // bound by copy

NSLog((NSString *)&__NSConstantStringImpl__var_folders_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_3146e1_mi_4,age,(*height));

}

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)};

int main(int argc, const char * argv[]) {

/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

static int height = 30;

int age = 20;

void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));

age = 25;

height = 35;

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

如图所示,`age`是直接值传递,`height`传递的是`*height` 也就是说直接把内存地址传进去进行修改了。

全局变量

运行代码

int age1 = 11;

static int height1 = 22;

int main(int argc, const char * argv[]) {

@autoreleasepool {

void (^block)(void) = ^{

NSLog(@"age1 is %d height1 = %d",age1,height1);

};

age1 = 25;

height1 = 35;

block();

}

return 0;

}

输出结果为

age1 is 25 height1 = 35

分析xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

生成`main.cpp`

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_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_4e8c40_mi_4,age1,height1);

}

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)};

int main(int argc, const char * argv[]) {

/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

age1 = 25;

height1 = 35;

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

}

return 0;

}

从cpp文件可以看出来,并没有捕获全局变量age1和height1,访问的时候,是直接去访问的,根本不需要捕获

小结auto修饰的局部变量,是值传递

static修饰的局部变量,是指针传递

其实也很好理解,因为auto修饰的局部变量,离开作用域就销毁了。那如果是指针传递的话,可能导致访问的时候,该变量已经销毁了。程序就会出问题。而全局变量本来就是在哪里都可以访问的,所以无需捕获。

block类型

block也是一个OC对象

在进行分析block类型之前,先明确一个概念,那就是block中有isa指针的,block是一个OC对象,例如下面的代码

void (^block)(void) = ^{

NSLog(@"123");

};

NSLog(@"block.class = %@",[block class]);

NSLog(@"block.class.superclass = %@",[[block class] superclass]);

NSLog(@"block.class.superclass.superclass = %@",[[[block class] superclass] superclass]);

NSLog(@"block.class.superclass.superclass.superclass = %@",[[[[block class] superclass] superclass] superclass]);

输出结果为

iOS-block[18429:234959] block.class = __NSGlobalBlock__

iOS-block[18429:234959] block.class.superclass = __NSGlobalBlock

iOS-block[18429:234959] block.class.superclass.superclass = NSBlock

iOS-block[18429:234959] block.class.superclass.superclass.superclass = NSObject

说明了上面代码中的block的类型是`__NSGlobalBlock`,继承关系可以表示为`__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject`

block有3种类型

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型`__NSGlobalBlock__ ( _NSConcreteGlobalBlock )`

`__NSStackBlock__ ( _NSConcreteStackBlock )`

`__NSMallocBlock__ ( _NSConcreteMallocBlock )`

其中三种不同的类型和环境对应如下

其在内存中的分配如下对应

运行代码查看

MRC下

**注意,以下代码在MRC下测试**

**注意,以下代码在MRC下测试**

** 注意,以下代码在MRC下测试 **

因为ARC的时候,编译器做了很多的优化,往往看不到本质,改为MRC方法: `Build Settings` 里面的`Automatic Reference Counting`改为NO

如下图所示

用代码来表示

void (^block)(void) = ^{

NSLog(@"123");

};

NSLog(@"没有访问auto block.class = %@",[block class]);

auto int a = 10;

void (^block1)(void) = ^{

NSLog(@"a = %d",a);

};

NSLog(@"访问了auto block1.class = %@",[block1 class]);

NSLog(@"访问量auto 并且copy block1-copy.class = %@",[[block1 class] copy]);

输出为

OS-block[23542:349513] 没有访问auto block.class = __NSGlobalBlock__

iOS-block[23542:349513] 访问了auto block1.class = __NSStackBlock__

iOS-block[23542:349513] 访问量auto 并且copy block1-copy.class = __NSStackBlock__

可以看出和上面说的

是一致的

ARC下

在ARC下,上面的代码输出结果为下面所示,因为编译器做了copy

iOS-block[24197:358752] 没有访问auto block.class = __NSGlobalBlock__

iOS-block[24197:358752] 访问了auto block1.class = __NSMallocBlock__

iOS-block[24197:358752] 访问量auto 并且copy block1-copy.class = __NSMallocBlock__

block的copy

前面说了在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,具体来说比如以下情况

copy的情况block作为函数返回值时

将block赋值给__strong指针时

block作为Cocoa API中方法名含有usingBlock的方法参数时

block作为GCD API的方法参数时

block作为函数返回值时

// 定义Block

typedef void (^YZBlock)(void);

// 返回值为Block的函数

YZBlock myblock()

{

int a = 6;

return ^{

NSLog(@"--------- %d",a);

};

}

YZBlock Block = myblock();

Block();

NSLog(@" [Block class] = %@", [Block class]);

输出为

iOS-block[25857:385868] --------- 6

iOS-block[25857:385868] [Block class] = __NSMallocBlock__

上述代码如果再MRC下输出`__NSStackBlock__`,在ARC下,自动copy,所以是`__NSMallocBlock__`

将block赋值给`__strong`指针时

// 定义Block

typedef void (^YZBlock)(void);

int b = 20;

YZBlock Block2 = ^{

NSLog(@"abc %d",b);

};

NSLog(@" [Block2 class] = %@", [Block2 class]);

输出为

iOS-block[26072:389164] [Block2 class] = __NSMallocBlock__

上述代码如果再MRC下输出`__NSStackBlock__`,在ARC下,自动copy,所以是`__NSMallocBlock__`

block作为Cocoa API中方法名含有usingBlock的方法参数时

eg:

NSArray *array = @[@1,@4,@5];

[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

// code

}];

block作为GCD API的方法参数时

eg

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

});

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

//code to be executed after a specified delay

});

MRC下block属性的建议写法

@property (copy, nonatomic) void (^block)(void);

ARC下block属性的建议写法

@property (strong, nonatomic) void (^block)(void);

@property (copy, nonatomic) void (^block)(void);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值