白水

iOS 模式注解—「Block 代码块」

引导


Block 在开发中是比较实用的,一般能用Block的就使用Block,学会使用Block,你再也不想用繁琐的代理。Block没有你想象中的那么难,不要害怕,要勇敢尝试。

本篇文章主要从【Block 使用 / 场景】学习总结,该模块将系统化学习,后续替换、补充文章内容 ~
在「时间 & 知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出,可留言指正或是补充,以提高文章质量@CoderLN著;

目录:

  1. Block 概念
  2. Block 定义、回调
  3. Block 内容管理 (MRC)
  4. Block 为什么使用 copy ?
  5. Block 循环引用
  6. 场景:Block变量传递
  7. 场景:Block参数使用
  8. 场景:Block当返回值
  9. Block 分类(栈、堆、全局区)
  10. Block 底层实现
  11. SourceCodeToolsClassWechatPublic-Codeidea

Block概念

Block 释义:
匿名的函数、代码块(把你想要执行的代码封装在这个代码块里,等到需要的时候再去调用),在 iOS4 开始引入的对 C 语言的扩展,用来实现匿名函数的特性,Block 是一种特殊的数据类型,可以正常定义变量、作为参数、作为返回值;还可以保存一段代码,目前Block 已经广泛应用于iOS开发中,常用于传值、各类回调等;

Block定义、回调

【Block格式】: 返回值(^Block类型) (参数1, 参数2) = ^返回值类型 (参数列表) {函数体};

  • 【了解:无实用】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    - (void)viewDidLoad {
    [super viewDidLoad];

    // 1.无返回值有参数a和b, 类型为LNBlock1
    // 手写Block - 快捷键(inlineBlock)
    void(^LNBlock1)(NSString *a, NSInteger b) = ^(NSString *name, NSInteger age) {
    NSLog(@"手写Block - 无返回值有参数a和b,姓名:%@ 年龄:%ld",name,age);
    };
    LNBlock1(@"白开水ln",25); // 手写Block - 回调


    // 2.有返回值有参数a, 类型为LNBlock2
    int(^LNBlock2)(int a) = ^(int A){
    return A;
    };
    NSLog(@"%d",LNBlock2(25)); // 回调
    }
  • Block跨方法调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
1.【定义方式一】: 把Block定义成变量(起别名)

// 无返回值无参数, 类型为MyBlockType
typedef void(^MyBlockType)(void);

@property (nonatomic, copy) MyBlockType myBlock;【变量名】

- (void)viewDidLoad {
[super viewDidLoad];

self.myBlock = ^{
NSLog(@"把Block定义成变量 - 实现Block, 保存代码接收值");
};
}

- (void)touchesBegan:
{
// 这里严谨点,要加上判断
if (self.myBlock) {
self.myBlock(); // 把Block定义成变量 - 回调
}
}


2.【定义方式二】: 把Block定义成属性

// 有返回值有参数, 变量名为MyBlockType
@property (nonatomic, copy) NSString * (^myBlock)(NSString *name);

- (void)viewDidLoad {
[super viewDidLoad];

self.myBlock = ^NSString *(NSString *name) {
NSLog(@"把Block定义成属性 - 实现Block, 保存代码接收值,name = %@",name);
return @"Block定义有返回值的属性";
//return name;
};
}

- (void)touchesBegan:
{
// 这里严谨点,要加上判断
if (self.myBlock) {
NSLog(@"return - %@",self.myBlock(@"白开水ln")); // 把Block定义成属性 - 回调传值
}
}


3.【定义方式三】: 把Block定义成方法

B.h文件
B-1.定义Block【定义成方法: 有返回值有参数】
- (void)loadWithData:(NSInteger (^)(NSString *name, NSInteger age1, NSInteger age2))syBlock;
@property (nonatomic, assign) NSInteger sumAge; // 接收Block返回结果

B.m文件
B-2.回调传值, 将Block返回值赋值给属性
-(void)loadWithData:(NSInteger (^)(NSString *, NSInteger, NSInteger))syBlock
{
if (syBlock) {
_sumAge = syBlock(@"syh",26,34);
}
}



A.h文件
// A-1.实现Block
[aVc loadWithData:^NSInteger (NSString *name, NSInteger age1, NSInteger age2) {
NSLog(@"name = %@, age1 = %ld, age2 = %ld", name, age1, age2); // A-2.保存代码接收值
return age1 + age2; // 将Block返回值赋值给属性sumAge
}];
NSLog(@"%ld",aVc.sumAge);
  • Block跨类调用 (使用步骤)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
Block传值: B (定义Block, 回调传值) -> A (实现Block, 保存代码接收值)
*/


B.h文件
// B-1.定义Block (定义成属性)
// 提示:你要写什么(缺什么就补什么); 开始不要想太多,就写个纯净的block
@property (nonatomic, strong) void(^myBlock)(NSString *name);

B.m文件
- (void)touchesBegan:
{
// B-2.回调传值
if (self.myBlock) {
self.myBlock(@"name = 我是Block逆传");
}
[self dismissViewControllerAnimated:YES completion:nil];
}


A.m文件
- (void)touchesBegan:
{
TwoViewController * twoVc = [[TwoViewController alloc] init];
// A-1.实现Block
twoVc.myBlock = ^(NSString *name) {
NSLog(@"%@",name); // A-2.保存代码接收值
};

[self presentViewController:twoVc animated:YES completion:nil];
}

Block内容管理 (MRC)

  • 【了解:MRC开发常识】
    • MRC没有strong,weak,局部变量对象就是相当于基本数据类型(基本数据类型放在栈里,代码块结束就销毁)。
    • MRC给成员属性赋值,一定要使用set方法,不能直接访问下划线成员属性赋值.
    • dealloc 能否调用super (只有MRC才能调用super)
    • 能否使用retain,release. 如果能用就是MRC
  • 【代码:】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void(^block)(void) = ^{

};
NSLog(@"%@",block); // 1.LNBlock[23533:817404] <__NSGlobalBlock__: 0x102080320>


//static int a = 3; // 静态变量
__block int a = 3; // 还是局部变量
void(^block)(void) = ^{
NSLog(@"%d",a); // 引用外部局部变量
};
NSLog(@"%@",block); // 2.LNBlock[2328:54102] <__NSMallocBlock__: 0x60000044c810>


@property (nonatomic, copy) void(^block)(void); // 3.LNBlock[2683:63901] <__NSGlobalBlock__: 0x1043c3090>

总结:
1、只要block没有引用外部局部变量,block放在全局区。
2、只要Block引用外部局部变量,block放在栈里面。
3、block使用copy,不能使用retain. 原因: 使用retain,block还是在栈里面(代码块结束就销毁),而使用copy,block就在全局区。

Block循环引用

循环引用:我引用你,你也引用,就会造成循环引用,双方都不会被销毁,导致内存泄露问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//【需求】:Block里面要访问控制器对象做些事情,这时如果Block内部有延迟操作,就会拿不到控制器对象。
//【解决】:下面有图解 (代码 + 思路)
__weak typeof(self) weakSelf = self;

_block = ^{

__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // GCD延迟执行

NSLog(@"strongSelf = %@",strongSelf);
});

NSLog(@"weakSelf = %@",weakSelf);
};

_block();// 调用

/**
LNBlock[3836:101980] weakSelf = <TwoViewController: 0x7fbfcfd413c0>
LNBlock[3836:101980] strongSelf = <TwoViewController: 0x7fbfcfd413c0>
LNBlock[3836:101980] dealloc = TwoViewController销毁
*/

场景:Block变量传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1.如果是局部变量, Block是【值传递】(外面修改不影响里面)
int a = 3;
void(^block1)() = ^{
NSLog(@"block1--%d",a); // a为3
};
a = 5;
block1();

//--------------------------- <#我是分割线#> ------------------------------//

// 2.如果是(static静态变量、int全局变量、__block修饰的变量),block都是【指针传递】(外面的修改影响block里面的值)
__block int b = 3;
void(^block2)() = ^{
NSLog(@"block2--%d",b); // b为5
};
b = 5;
block2();

场景:Block参数使用

1、怎么区分参数是block,就看有没有 ^,只要有^ 把block当做参数
2、把block当做参数,并不是马上就调用Block,什么时候调用,由方法内部决定
3、什么时候需要把block当做参数去使用:做的事情由外界(非本类)决定,但是什么时候做由内部决定。就如:UIView动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
* ViewController.m文件 *

- (void)viewDidLoad {
[super viewDidLoad];

//【需求】:封装一个计算器,提供一个计算方法;【怎么计算 由外界决定,什么时候计算 由内部决定.】
// 创建计算器管理者
LNSumManager *mgr = [[LNSumManager alloc] init];
[mgr sum:^NSInteger(NSInteger result) {
result += 5;
result *= 2;
return result;
}];
NSLog(@"%ld",mgr.result); // 14
}

- - -

* LNSumManager.h文件 *

@property (nonatomic, assign) NSInteger result; // 保存计算结果
// 3.给Block定义成方法
- (void)sum:(NSInteger(^)(NSInteger result))sumBlock;


* LNSumManager.m文件 *

- (void)sum:(NSInteger (^)(NSInteger))sumBlock
{
_result = 2; // 初始值为2
if (sumBlock) {
_result = sumBlock(_result);
}
}

场景:Block当返回值

这里把Block当返回值,体现了一种思想,链式编程思想:把所有的语句用.号连接起来,好处:可读性非常好)。
如: Mansonry 里的 make.center.equalTo(self.view);
解析:make.center.equalTo 得到一个 block,make.center.equalTo(self.view) 去调用 block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
* ViewController.m文件 *

- (void)viewDidLoad {
[super viewDidLoad];

//【需求】:封装一个计算器, 提供一个加号方法;
// 创建计算器管理者
CalculatorManager *mgr = [[CalculatorManager alloc] init];

mgr.add(5).add(5).add(5).add(5).add(5);
NSLog(@"%d",mgr.result); // 25
}

- - -

* CalculatorManager.h文件 *

@property (nonatomic, assign) int result; // 保存计算结果

// 3.给Block定义成方法
- (CalculatorManager *(^)(int))add;


* CalculatorManager.m文件 *

- (CalculatorManager *(^)(int))add
{
return ^(int value){
_result += value;
return self;
};
}

Block分类(栈、堆、全局区)

  • block 块的存储位置(block入口的地址)可能存放在3个地方:代码区(全局区)、堆区、栈区(ARC情况下回自动拷贝到堆区、因此ARC下只有两个地方:代码区和堆区)。

    • NSStackBlock:栈
      特点:生命周期由系统控制,函数返回即销毁
      用到局部变量、成员属性\变量,且没有强指针引用的block都是栈block。
    • NSMallocBlock:堆
      特点:没有强指针引用即销毁,生命周期由程序员手动管理
      栈block如果有强指针引用或copy修饰的成员属性引用就会被拷贝到堆中,变成堆block。
    • NSGlobalBlock:全局区
      特点:生命期长(应用程序在它就在)
      没有用到外界变量,或者只用到全局变量、静态(static)变量的block就是全局block。

Block底层实现

block是一个指针结构体,在终端下使用LLVM编译器的clang命令可将含有Block的Objective-C代码转换成C++的源代码,以探查其具体实现方式:clang -rewrite-objc 源码文件名

创建一个OS X 工程,写一个最简单的block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^myblock)() = ^() {
NSLog(@"hello block");
};
myblock();
}
return 0;
}

利用终端编译生成C++代码:clang -rewrite-objc main.m


几个重要的结构体和函数简介:

__block_impl:这是一个结构体,也是C面向对象的体现,可以理解为block的基类;
__main_block_impl_0: 可以理解为block变量;
__main_block_func_0: 可以理解为匿名函数;
__main_block_desc_0: block的描述,Block_size;

注解:
block容易造成循环引用,在block里面如果使用了self,然后形成强引用时,需要打断循环引用;在MRC下用 __block,在ARC下使用 __weak;

模拟数据请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
模拟数据请求
*/
- (void)viewDidLoad {
[super viewDidLoad];

[self loadData:^(NSString *name) {
NSLog(@"%@",name);
}];
}

-(void)loadData:(void (^)(NSString *name))callBlock
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"发送数据请求 - %@",[NSThread currentThread]);

dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"拿到数据,进行回调 - %@",[NSThread currentThread]);
});

if (callBlock) {
callBlock(@"json数据返回"); // 回调传值
}
});
}

Reading


  • 如果在阅读过程中遇到 Error || New ideas,希望你能 issue 我,我会及时补充谢谢。

  • 熬夜写者不易,喜欢可 赞赏 or Star 一波;点击左上角关注 或 『Public:Codeidea』,在 Demo | 文章 | Rss 更新时收到提醒通知,便捷阅读。

既然阅读完了    就在下面留言吧!

标题:iOS 模式注解—「Block 代码块」

作者:白水

链接:http://githubidea.github.io/iOSUI/Block.html

版权声明: 署名-非商业性使用-禁止演绎 4.0 国际 本博客文章除特别声明外均为原创,如需转载请务必保留原链接(可点击进入的链接)和 作者出处。