Block

1.Block简介

Block以状况,可以以简单个界面的传值,也得对代码封装作为参数的传递等。用过GCD就清楚Block的精密的处在。

 

Block是如出一辙栽于新鲜之数据类型。它可保留一截代码,在适当的时获得出来调用。

 

2.Block
底层实现之规律

故而Sublime Text建立一个Objective-C的文书,保存命名为BlockOne:

 

 

下一场写副下的代码:

 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;

桌面会多生一个文本夹:

用 Xcode打开它:

 

概念完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 }

 

 

随即是一个函数的落实,对应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变量的。

连无是直传递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用法和落实原理