Block

1.Block简介

Block使用境况,可以在五个界面的传值,也可以对代码封装作为参数的传递等。用过GCD就知道Block的小巧之处。

 

Block是一种相比新鲜的数据类型。它可以保留一段代码,在合适的时候取出来调用。

 

2.Block
底层实现的法则

用Sublime Text建立一个Objective-C的公文,保存命名为BlockOne:

 

图片 1

 

然后写入下边的代码:

 1 #import <Foundation/Foundation.h>
 2 
 3         int main(int argc, const char * argv[])
 4          {
 5              @autoreleasepool 
 6              {
 7                 void (^huanggang)() = ^{printf("Hello World");};
 8 
 9                 huanggang();
10             }
11             return 0;
12          }

入选这一个文件BlockOne,双击它,选中“展现简介”–>”名称与扩展名”–>修改为“BlockOne.m”–>打开终端–>输入
cd desktop –>输入 

clang -rewrite-objc BlockOne.m;

桌面会多出一个文书夹:

图片 2

用 Xcode打开它:

图片 3

 

概念完block之后,其实是创制了一个函数,在创造结构体的时候把函数的指针一起传给了block,所以随后可以拿出来调用。

看值捕获的的问题:

 1 #import <Foundation/Foundation.h>
 2 
 3 int main(int argc, const char * argv[])
 4 {
 5     @autoreleasepool
 6     {
 7 
 8         int a = 10;
 9         void (^huanggang)() = ^{printf("a= %d",a);};
10         
11         huanggang();
12     }
13     return 0;
14 }

 

 图片 4

这是一个函数的贯彻,对应Block中的 {} 内的始末,这么些情节作为了 C
语言函数来拍卖,函数参数中的 _cself 相当于 Objective-C 中的 self。

Desc : 描述了 Block 大小、版本消息;

1 impl.isa = &_NSConcreteStackBlock; //在函数栈上声明,则为_NSConcreteStackBlock

__main_block_impl_0即为main()函数栈上的Block结构体,其中的__block_impl结构体讲明如下:

1 struct __block_impl
2  {
3   void *isa;//指明对象的Class
4   int Flags;
5   int Reserved;
6   void *FuncPtr;
7 };

__block_impl结构体,即为Block的结构体,可知道为Block的类社团。
再看下main()函数翻译的情节:

1 int main() {
2     int count = 10;
3     void (* huanggang)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, count));
4 
5     ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)huanggang);
6 }

剔除掉复杂的项目转化,可简写为:

1 int main() {
2     int count = 10;
3     sturct __main_block_impl_0 *huanggang = &__main_block_impl_0(__main_block_func_0,         //函数指针
4                                                            &__main_block_desc_0_DATA)); //Block大小、版本等信息
5 
6     (*huanggang->FuncPtr)(huanggang);   //调用FuncPtr指向的函数,并将blk自己作为参数传入
7 }

经过,可以看到,Block也是Objective-C中的对象

 

定义block的时候,变量a的值就传递到了block结构体中,仅仅是值传递,所以在block中修改a是不会潜移默化到外边的a变量的。

图片 5

并不是一向传递a的值了,而是把a的地址传过去了,所以在block内部便足以修改到外围的变量了。

依照isa指针,block一共有3类别型的block
_NSConcreteGlobalBlock 全局静态
_NSConcreteStackBlock 保存在栈中,出函数功效域就销毁
_NSConcreteMallocBlock 保存在堆中,retainCount == 0销毁
而ARC和MRC中,还略有不同

 

观测上节代码中__main_block_impl_0结构体(main栈上Block的结构体)的构造函数可以看看,栈上的变量
a 以参数的花样传播到了这么些构造函数中,此处即为变量的自发性截获
由此可以这么领悟:__block_impl结构体已经可以表示Block类了,但在栈上又声称了__main_block_impl_0结构体,对__block_impl进行封装后才来表示栈上的Block类,就是为着拿到Block中动用到的栈上表明的变量(栈上没在Block中使用的变量不会被擒获),变量被保留在Block的布局体实例中。
故而在blk()执行此前,栈上简单数据类型的count无论爆发什么样变动,都不会影响到Block以参数形式传播而抓获的值。但以此变量是指向目的的指针时,是可以修改那么些目的的性能的,只是不可能为变量重新赋值。

2.1 Block的存储域

据悉Block创设的职务不同,Block有三系列型,创制的Block对象分别会蕴藏到栈、堆、全局数据区域。

 2.1.2
全局区

 

1 void (^blk)(void) = ^{
2     NSLog(@"Global Block");
3 };
4 
5 int main() {
6     blk();
7     NSLog(@"%@",[blk class]);    //打印:__NSGlobalBlock__
8 }

 

 

 

 

 2.1.2 堆区

 1 int main() {
 2     void (^blk)(void) = ^{//没有截获自动变量的Block
 3         NSLog(@"Stack Block");
 4     };
 5     blk();
 6     NSLog(@"%@",[blk class]);//打印:__NSGlobalBlock__
 7 
 8     int i = 1;
 9     void (^captureBlk)(void) = ^{//截获自动变量i的Block
10         NSLog(@"Capture:%d", i);
11     };
12     captureBlk();
13     NSLog(@"%@",[captureBlk class]);//打印:__NSMallocBlock__
14 }

 

可以看出截获了电动变量的Block打印的类是NSGlobalBlock,表示存储在全局数据区。但捕获自动变量的Block打印的类却是设置在堆上的NSMallocBlock,而非栈上的NSStackBlock。

 

2.12  Block复制

布置在栈上的Block,假设其所属的栈功效域截止,该Block就会被废除,对于超出Block功能域仍需使用Block的动静,Block提供了将Block从栈上复制到堆上的章程来解决这种问题,即使Block栈效能域已了结,但被拷贝到堆上的Block还足以继承存在。
复制到堆上的Block,将_NSConcreteMallocBlock类对象写入Block结构体实例的成员变量isa:

1 impl.isa = &_NSConcreteMallocBlock;

 

在ARC有效时,大多数气象下编译器会展开判定,自动生成将Block从栈上复制到堆上的代码,以下两种意况栈上的Block会自动复制到堆上

  • 调用Block的copy方法
  • 将Block作为函数重临值时
  • 将Block赋值给__strong修改的变量时
  • 向Cocoa框架含有usingBlock的点子或者GCD的API传递Block参数时

其它时候向方法的参数中传送Block时,需要手动调用copy方法复制Block。
上一节的栈上截获了活动变量i的Block之所以在栈上创设,却是NSMallocBlock_类,就是因为那么些Block对象赋值给了_strong修饰的变量captureBlk(strong是ARC下对象的默认修饰符)。
因为下边四条规则,在ARC下实际很少见到_NSConcreteStackBlock类的Block,大多数气象编译器都保证了Block是在堆上创造的,如下代码所示,仅最终一行代码直接动用一个不赋值给变量的Block,它的类才是__NSStackBlock:

1 int count = 0;
2     blk_t blk = ^(){
3         NSLog(@"In Stack:%d", count);
4     };
5 
6     NSLog(@"blk's Class:%@", [blk class]);//打印:blk's Class:__NSMallocBlock__
7     NSLog(@"Global Block:%@", [^{NSLog(@"Global Block");} class]);//打印:Global Block:__NSGlobalBlock__
8     NSLog(@"Copy Block:%@", [[^{NSLog(@"Copy Block:%d",count);} copy] class]);//打印:Copy Block:__NSMallocBlock__
9     NSLog(@"Stack Block:%@", [^{NSLog(@"Stack Block:%d",count);} class]);//打印:Stack Block:__NSStackBlock__

 

2.13 使用__block

Block捕获的活动变量添加__block表明符,就可在Block内读和写该变量,也可以在原本的栈上读写该变量。
机动变量的收缴管教了栈上的自动变量被销毁后,Block内仍可利用该变量。
__block管教了栈上和Block内(通常在堆上)可以访问和改动“同一个变量”,__block是何许贯彻这一职能的?

__block发挥成效的原理:将栈上用__block修饰的电动变量封装成一个结构体,让其在堆上创制,以方便从栈上或堆上访问和修改同一份数据。

 

2.14 Block 的循环引用

Block的巡回引用原理和化解形式大家都相比较熟稔,此处将结合上文的牵线,介绍一种不常用的化解Block循环引用的艺术和一种借助Block参数解决该问题的不二法门。
Block循环引用原因:一个对象A有Block类型的习性,从而具有这多少个Block,如若Block的代码块中动用到这多少个目的A,或者只有是用用到A对象的性能,会使Block也持有A对象,导致二者互相持有,不可能在功能域截至后健康释放。
缓解原理:对象A照常持有Block,但Block无法强引用持有对象A以打破循环。
缓解情势
方法一:
对Block内要拔取的靶子A使用_**_weak**举行修饰,Block对目标A弱引用打破循环。

有两种常用格局:

  • 使用__weak ClassName

    1 __block XXViewController* weakSelf = self;
    2 self.blk = ^{
    3 NSLog(@”In Block : %@”,weakSelf);
    4 };

 

  • 使用__weak typeof(self)

    __weak typeof(self) weakSelf = self;

    self.blk = ^{
        NSLog(@"In Block : %@",weakSelf);
    };
    
  • Reactive Cocoa中的@weakify和@strongify

    1 @weakify(self);
    2 self.blk = ^{
    3 @strongify(self);
    4 NSLog(@”In Block : %@”,self);
    5 };

艺术二:对Block内要选取的靶子A使用__block举行修饰,并在代码块内,使用完__block变量后将其设为nil,并且该block必须至少实施五遍。

 

未修正前:

1  __block XXController *blkSelf = self;
2     self.blk = ^{
3         NSLog(@"In Block : %@",blkSelf);
4     };

 

专注上述代码仍存在内存泄露,因为:

  • XXController对象拥有Block对象blk
  • blk对象拥有__block变量blkSelf
  • __block变量blkSelf持有XXController对象

 

修正后:

1 __block XXController *blkSelf = self;
2     self.blk = ^{
3         NSLog(@"In Block : %@",blkSelf);
4         blkSelf = nil;//不能省略
5     };
6 
7     self.blk();//该block必须执行一次,否则还是内存泄露

 

 

在block代码块内,使用完使用完__block变量后将其设为nil,并且该block必须至少实施一回后,不设有内存泄露,因为此时:

  • XXController对象拥有Block对象blk
  • blk对象具备__block变量blkSelf(类型为编译器创造的结构体)
  • __block变量blkSelf在实施blk()之后被设置为nil(__block变量结构体的__forwarding指针指向了nil),不再持有XXController对象,打破循环

其次种选拔__block打破循环的艺术,优点是:

  • 可通过__block变量动态控制持有XXController对象的刻钟,运行时控制是否将nil或任何变量赋值给__block变量
  • 无法应用__weak的系列中,使用__unsafe_unretained来替代__weak打破循环可能有野指针问题,使用__block则可避免该问题

缺点也明显:

  • 必须手动保证__block变量最终设置为nil
  • block必须执行一遍,否则__block不为nil循环应用仍存在

由此,仍然制止使用第两种不常用格局,直接行使__weak打破Block循环引用。
方法三:将在Block内要运用到的对象(一般为self对象),以Block参数的款式传播,Block就不会捕获该目标,而将其看做参数使用,其生命周期系统的栈自动管理,不造成内存泄露。

即原来采取__weak的写法

__weak typeof(self) weakSelf = self;
    self.blk = ^{
        __strong typeof(self) strongSelf = weakSelf;
        NSLog(@"Use Property:%@", strongSelf.name);
        //……
    };
    self.blk();

 改为Block传参写法后:

1  self.blk = ^(UIViewController *vc) {
2         NSLog(@"Use Property:%@", vc.name);
3     };
4     self.blk(self);

优点:

  • 简化了两行代码,更优雅
  • 更显眼的API设计:告诉API使用者,该措施的Block直接运用传进来的参数对象,不会导致循环引用,不用调用者再利用weak制止循环

 

3.Block的修饰

ARC情况下

3.1、假诺用copy修饰Block,该Block就会储存在堆空间。则会对Block的其中对象开展强引用,导致循环引用。内存不可以自由。

缓解情势:

新建一个指南针(__weak typeof(Target) weakTarget = Target
)指向Block代码块里的对象,然后用weakTarget进行操作。就可以化解循环引用问题。

3.2、假设用weak修饰Block,该Block就会存放在栈空间。不会油不过生循环引用问题。

MRC情况下

用copy修饰后,假如要在Block内部选择对象,则需要举办(__block
typeof(Target) blockTarget = Target
)处理。在Block里面用blockTarget举办操作。

 

 

typedef int (^SumP)(int,int);//用typedef定义一个block类型

 

 1 //利用typedef定义block来创建一个block变量
 2 
 3    SumP sumblock1 = ^(int a,int b){
 4 
 5        return a - b;
 6 
 7     };
 8 
 9    int d = sumblock1(10,5);
10 
11     NSLog(@"d = %d",d);
12 
13  

 

 4**.Block的三种格式

4.1、Block的定义格式

归来值类型(^block变量名)(形参列表) = ^(形参列表)

{

};

调用Block保存的代码

block变量名(实参);

默认情状下,Block内部不可能修改外面的一些变量
Block内部能够修改使用__block修饰的有的变量

4.2、Block的模式

 

Block简单用法举例

4.2.1.无参数无再次回到值的Block

 

参数列表为空可以大概为:

1 ^ {
2       NSLog(@"No Parameter");
3   };

 

最减格局语法为:

^ {    表达式  }

 

实际一点的有:

 1 /**
 2 
 3  *  无参数无返回值的Block
 4 
 5  */
 6 
 7 -(void)func1{
 8 
 9     /**
10 
11      *  void :就是无返回值
12 
13      *  emptyBlock:就是该block的名字
14 
15      *  ():这里相当于放参数。由于这里是无参数,所以就什么都不写
16 
17      */
18 
19     void (^emptyBlock)() = ^(){
20 
21         NSLog(@"无参数,无返回值的Block");
22 
23     };
24 
25     emptyBlock();
26 
27 }
28 
29  

 

 

4.2.2.有参数无再次来到值的Block

 

 1 /**
 2 
 3      *  调用这个block进行两个参数相加
 4 
 5      *
 6 
 7      *  @param int 参数A
 8 
 9      *  @param int 参数B
10 
11      *
12 
13      *  @return 无返回值
14 
15      */
16 
17     void (^sumBlock)(int ,int ) = ^(int a,int b){
18 
19         NSLog(@"%d + %d = %d",a,b,a+b);
20 
21     };
22 
23     /**
24 
25      *  调用这个sumBlock的Block,得到的结果是20
26 
27      */
28 
29     sumBlock(10,10);

 

 

4.2.3.有参数有再次来到值的Block

 

最简易的:

1 ^ int (int count) 
2 {
3         return count + 1;
4  };

其中,可粗略部分有:

  • 回来类型,例:

    1 ^ (int count)
    2 {
    3 return count + 1;
    4 };

 

 

 1 /**
 2 
 3      *  有参数有返回值
 4 
 5      *
 6 
 7      *  @param NSString 字符串1
 8 
 9      *  @param NSString 字符串2
10 
11      *
12 
13      *  @return 返回拼接好的字符串3
14 
15      */    
16 
17     NSString* (^logBlock)(NSString *,NSString *) = ^(NSString * str1,NSString *str2){
18 
19         return [NSString stringWithFormat:@"%@%@",str1,str2];
20 
21     };
22 
23     //调用logBlock,输出的是 我是Block
24 
25     NSLog(@"%@", logBlock(@“我是",@"Block"));
26 
27  

 

扬言了一个变量名为blk的Block:

1     int (^blk)(int) = ^(int count)
2     {
3         return count +1;
4     };

 

当Block类型变量作为函数的参数时,写作:

1 - (void)func:(int (^)(int))blk
2 {
3     NSLog(@"param:%d",blk(4));
4 }

 

在 – (void) viewDidLoad中调用

1     [self func:^int(int A) {
2         NSLog(@"alksjga;lg");
3         return A +=3;;
4         
5     }];

 

打印结果为:

2017-07-27 03:23:06.227 Block_Demo[14689:780926] alksjga;lg
2017-07-27 03:23:09.537 Block_Demo[14689:780926] param:7

 

typedef可简写:

1 typedef int (^blk_k) (int);
2 
3 
4 - (void)funcOne:(blk_k)blk
5 {
6     NSLog(@"param:%d",blk(5));
7 }

 

在viewDidLoad中调用:

1 [self funcOne:^int(int a) {
2         return  a += 2;
3     }];

 

打印结果为:

2017-07-27 03:29:09.593 Block_Demo[14689:780926] param:7

 

 

 

4.3、Block结合typedef使用

投机定义一个Block类型,用定义的项目去创建Block,更加简约方便。

此地举例一个Block回调修改上一下界面的背景颜色。

ViewController1 控制器1,ViewController2 控制器2

控制器1跳转到控制器2,然后在控制器2触发事件回调修改决定器1的背景颜色为革命。

 

Demo

ViewController2的实现

 1 #import <UIKit/UIKit.h>
 2 
 3 @interfaceViewController2 : UIViewController
 4 
 5 /**
 6 
 7  *  定义了一个changeColor的Block。这个changeColor必须带一个参数,这个参数的类型必须为id类型的
 8 
 9  *  无返回值
10 
11  *  @param id
12 
13  */
14 
15 typedefvoid(^changeColor)(id);
16 
17 /**
18 
19  *  用上面定义的changeColor声明一个Block,声明的这个Block必须遵守声明的要求。
20 
21  */
22 
23 @property (nonatomic, copy) changeColor backgroundColor;
24 
25 @end
26 
27 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
28 
29     //声明一个颜色
30 
31     UIColor *color = [UIColor redColor];
32 
33     //用刚刚声明的那个Block去回调修改上一界面的背景色
34 
35     self.backgroundColor(color);
36 
37 }
38 
39  

 

 

ViewController1的实现

 1 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
 2 
 3     ViewController2 *vc =[[ViewController2 alloc]init];
 4 
 5     // 回调修改颜色
 6 
 7     vc.backgroundColor = ^(UIColor *color){
 8 
 9         self.view.backgroundColor = color;
10 
11     };
12 
13     [self.navigationController pushViewController:vc animated:YES];
14 
15 }

 

 

5.Block
截值

 

5.1收缴自动变量值

 

Block表明式可收获所使用的机动变量的值。
缴枪:保存自动变量的刹那间值。
因为是“须臾间值”,所以表明Block之后,尽管在Block外修改自动变量的值,也不会对Block内缴获的自发性变量值暴发潜移默化。
例如:

1 int i = 10;
2     void (^blk)(void) = ^{
3         NSLog(@"In block, i = %d", i);
4     };
5     i = 20;//Block外修改变量i,也不影响Block内的自动变量
6     blk();//i修改为20后才执行,打印: In block, i = 10
7     NSLog(@"i = %d", i);//打印:i = 20

 

5.2 __block表明符号

机动变量截获的值为Block表明时刻的一念之差值,保存后就不可以改写该值,如需对电动变量举办重复赋值,需要在变量注脚前附加__block表达符,这时该变量称为__block变量。
例如:

 1 __block int i = 10;//i为__block变量,可在block中重新赋值
 2     void (^blk)(void) = ^{
 3         NSLog(@"In block, i = %d", i);
 4     };
 5     i = 20;
 6     blk();//打印: In block, i = 20
 7     NSLog(@"i = %d", i);//打印:i = 20
 8 

 

 

5.3 自动变量值为一个对象意况

当自动变量为一个类的对象,且并未拔取__block修饰时,即使不可以在Block内对该变量举行双重赋值,但可以修改该对象的属性。
一旦该目标是个Mutable的目的,例如NSMutableArray,则还足以在Block内对NSMutableArray进行元素的增删:

1 NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:@"1", @"2",nil ];
2     NSLog(@"Array Count:%ld", array.count);//打印Array Count:2
3     void (^blk)(void) = ^{
4         [array removeObjectAtIndex:0];//Ok
5         //array = [NSNSMutableArray new];//没有__block修饰,编译失败!
6     };
7     blk();
8     NSLog(@"Array Count:%ld", array.count);//打印Array Count:1

 

 

 

参考:Block用法和实现原理