Post

Objc - Block

Objc - Block

引言

Apple 在iOS4.0之后推出Block,它本身封装了一段代码并可以将这段代码当做变量,参数,属性,数据类型,函数(匿名),代码块(只有在被调用时才会执行)等等,使用方式灵活,功能十分强大。

Block的简单使用

定义Block

  • 无参无返回值
    1
    2
    3
    4
    
    void(^block)() = ^(){
     NSLog(@"this is a no param and no return of block");
     };
     block();
    
  • 有参无返回值
    1
    2
    3
    4
    
     void(^block)(NSString * ) = ^(NSString * param){
     NSLog(@"this is a has param and no return of block");
     };
     block(@"param");
    
  • 有参有返回值
    1
    2
    3
    4
    5
    
     NSString *(^block)(NSString * ) = ^(NSString * param){
     NSLog(@"this is a has param and return of block");
     return @"";
     };
     block(@"param");
    

定义Block的快捷方式

Block 的定义可以借助键入 inlineBlock 速记.

1
2
3
4
// inlineBlock
returnType (^blockName)(parameterTypes) = ^(parameters) {
   statements
};

Block定义成属性

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

Block作参数

1
2
3
4
5
6
7
8
9
10
11
-(void)demo04{
 void(^block)() = ^{
   NSLog(@"block become param");
 };
 [self callbackWith:block];
}

-(void)callbackWith:(void(^)())block{
 //调用外界传入的block
 block();
}

typedef 定义Block

1
2
3
4
5
6
7
// 声明
typedef void(^MyBlock)();
// 使用
MyBlock block01 = ^{
 NSLog(@"block01");
};
block01();
  • 小结:方便之处在于可以常用类型的Block可以用typedef来定义

    Block与外部变量

    Block内部访问/引用 外部变量

    1
    2
    3
    4
    5
    6
    7
    8
    
     int num = 10;
     NSLog(@"%p %d",&num,num);
     void(^block)() = ^{
     NSLog(@"%p %d",&num,num);
     };
     num = 20;
     NSLog(@"%@ %p %d",block,&num,num);
     block();
    
  • MRC输出结果:
    1
    2
    3
    
    018-05-10 12:33:49.178446+0800 BlockDemo[1825:245809] 0x7ffee31dc9dc 10
    2018-05-10 12:33:49.178655+0800 BlockDemo[1825:245809] <__NSStackBlock__: 0x7ffee31dc9a8> 0x7ffee31dc9dc 20
    2018-05-10 12:33:49.178770+0800 BlockDemo[1825:245809] 0x7ffee31dc9c8 10
    
  • ARC输出结果:
    1
    2
    3
    
    2018-05-10 12:36:47.248638+0800 BlockDemo[1887:256355] 0x7ffee517f9dc 10
    2018-05-10 12:36:47.248856+0800 BlockDemo[1887:256355] <__NSMallocBlock__: 0x604000259530> 0x7ffee517f9dc 20
    2018-05-10 12:36:47.249115+0800 BlockDemo[1887:256355] 0x604000259550 10
    
  • 小结 在block中访问外部的变量时,会自动拷贝到内存一份并开辟新的地址,这就是深拷贝

Block内部修改外部变量

1
2
3
4
5
6
7
8
9
__block int num = 10;
 NSLog(@"%p %d",&num,num);
 void(^block)() = ^{
   num = 30;
   NSLog(@"%p %d",&num,num);
 };
 num = 20;
 NSLog(@"%@ %p %d",block,&num,num);
 block();
  • MRC输出结果:
    1
    2
    3
    
    2018-05-10 12:41:56.490868+0800 BlockDemo[2018:272473] 0x7ffee14519d8 10
    2018-05-10 12:41:56.491055+0800 BlockDemo[2018:272473] <__NSStackBlock__: 0x7ffee1451980> 0x7ffee14519d8 20
    2018-05-10 12:41:56.491165+0800 BlockDemo[2018:272473] 0x7ffee14519d8 30
    
  • ARC输出结果:
    1
    2
    3
    
    2018-05-10 12:43:13.402390+0800 BlockDemo[2047:277009] 0x7ffeea3149d8 10
    2018-05-10 12:43:13.402662+0800 BlockDemo[2047:277009] <__NSMallocBlock__: 0x60400024da70> 0x604000034cf8 20
    2018-05-10 12:43:13.402777+0800 BlockDemo[2047:277009] 0x604000034cf8 30
    
  • 小结 block内修改的外部变量,需要用__block修饰.在此后若被block访问修改,变量的内存地址会重新指向拷贝后开辟的新的内存地址.

验证一个想法

ARC/MRC中block内引用外部变量地址如何在堆栈中变化,通过将Block自身作为自身的参数传入

1
2
3
4
5
6
7
8
9
int num = 10;
   NSLog(@"%p %d",&num,num);
   void(^block)(void(^)()) = ^(void(^block2)()){
       NSLog(@"%@ %p %d",block2,&num,num);
   };
   num = 20;
   NSLog(@"%@ %p %d",block,&num,num);
   //本身作为参数传入并输出指针对象的堆栈
   block(block);
  • 1.在ARC中block内引用外部变量地址如何在堆栈中变化,输出结果:
1
2
3
2018-05-10 12:51:29.359318+0800 BlockDemo[2216:302301] 0x7ffee29ab9dc 10
2018-05-10 12:51:29.359563+0800 BlockDemo[2216:302301] <__NSMallocBlock__: 0x600000259230> 0x7ffee29ab9dc 20
2018-05-10 12:51:29.359708+0800 BlockDemo[2216:302301] <__NSMallocBlock__: 0x600000259230> 0x600000259250 10
  • 2.在MRC中block内引用外部变量地址如何在堆栈中变化,输出结果:
1
2
3
2018-05-10 12:46:27.120908+0800 BlockDemo[2153:287894] 0x7ffee8a699dc 10
2018-05-10 12:46:27.121106+0800 BlockDemo[2153:287894] <__NSStackBlock__: 0x7ffee8a699a8> 0x7ffee8a699dc 20
2018-05-10 12:46:27.121239+0800 BlockDemo[2153:287894] <__NSStackBlock__: 0x7ffee8a699a8> 0x7ffee8a699c8 10
  • 小结: ARC下,永远在堆区中。MRC下永远在栈区中

block内存管理(MRC/ARC)

函数体

当函数体不变时

1
2
3
4
void(^myBlock)() = ^ {
 NSLog(@"hello world");
};
NSLog(@"%@", myBlock);
  • 输出结果:
    1
    
    2018-05-09 22:59:07.637768+0800 BlockDemo[3288:930159] <__NSGlobalBlock__: 0x10afa20d8>
    
  • 小结: 不引用任何外部变量的 block 保存在全局区 NSGlobalBlock,如果Block没有引用外部变量,那么这个Block的函数体内部包装的代码都不会发生变化,而且执行效率高,保存在全局区;(类似不变的字符串存在常量区)

    当函数体可变时(即访问修改外部变量时)

    1
    2
    3
    4
    5
    
    int i = 10;
    void(^myBlock)() = ^ {
     NSLog(@"hello world %d", i);
    };
    NSLog(@"%@", myBlock);
    
  • 输出结果:
    1
    
    2018-05-09 23:03:01.082413+0800 BlockDemo[3349:946888] <__NSMallocBlock__: 0x600000440090>
    
  • 小结: 引用外部变量的 block 保存在 : ARC : 堆区 NSMallocBlock MRC : 栈区 NSStackBlock 因此在定义 block 属性时应该使用 copy 关键字,将 block 从栈区复制到堆区

Block属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//定义 block 属性
@property (nonatomic, copy) void (^demoBlock)();

-(void)blockDemo {
 int i = 10;
 void(^myBlock)() = ^ {
   NSLog(@"hello world %d", i);
 };

 NSLog(@"%@", myBlock);
 // 错误的写法,不会调用 setter 方法,MRC下,无法拷贝到堆区
 //`_demoBlock` = myBlock;
 // 正确的写法,调用 setter 方法,并且对 block 进行 copy
 self.demoBlock = myBlock;
 NSLog(@"%@", self.demoBlock);
}
  • MRC下输出结果:会执行copy操作,从栈区拷贝到堆区
    1
    2
    
    2018-05-10 12:03:17.374375+0800 BlockDemo[1225:138257] <__NSStackBlock__: 0x7ffee68509a8>
    2018-05-10 12:03:17.374581+0800 BlockDemo[1225:138257] <__NSMallocBlock__: 0x60000025dca0>
    
  • ARC下输出结果:不会执行copy操作,本身就在堆区
    1
    2
    
    2018-05-10 12:04:42.891691+0800 BlockDemo[1308:145791] <__NSMallocBlock__: 0x6040002471d0>
    2018-05-10 12:04:42.891902+0800 BlockDemo[1308:145791] <__NSMallocBlock__: 0x6040002471d0>
    

    : 为了避免程序员的麻烦,在 ARC 中,定义了引用外部变量的 block,系统默认都是在堆区的!

copy关键字的探讨

ARC (为什么可以用copy / strong

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)blockDemo
{
 int num = 10;
 void (^task)() = ^ {
   NSLog(@"%d",num);
 };
 // ARC : 堆区 ==> __NSMallocBlock__
 // ARC环境下,属性也是强引用,同时会copy
 // self.task = task;
 // ARC环境下,成员变量也是强引用,同时会copy
 `_task` = task;
 NSLog(@"%@ -- %@",task,`_task`);
}
  • 输出结果:
    1
    
    2018-05-09 23:14:43.139413+0800 BlockDemo[3468:994925] <__NSMallocBlock__: 0x60400024ea60> -- <__NSMallocBlock__: 0x60400024ea60>
    
  • 小结: 在ARC环境下,上述block本来就保存在堆区,给属性赋值的时候,调用setter方法时,只会给一个引用.故ARC下使用strong和copy的效果是一模一样的

MRC(为什么必须用copy

1
2
3
4
5
6
7
8
9
10
11
12
13
-(void)blockDemo
{
 int num = 10;
 void (^task)() = ^ {
   NSLog(@"%d",num);
 };
 // MRC : 栈区 ==> __NSStackBlock__
 // 这个赋值过程会copy,也会引用计数+1
 //    self.task = task;
 // 这个赋值过程不会copy,仅仅是引用计数+1,内存依然在栈区
 `_task` = task;
 NSLog(@"%@ -- %@",task,`_task`);
}
  • 输出结果:
    1
    
    2018-05-09 23:17:25.352415+0800 BlockDemo[3528:1006735] <__NSStackBlock__: 0x7ffee98cc9a8> -- <__NSStackBlock__: 0x7ffee98cc9a8>
    
  • 小结: 在MRC环境下,调用成员变量进行赋值,仅仅是引用计数加1;不会进行copy操作. 所以内存依然在栈区.

面试时如何回答?

当Block被引入到OC时 ,OC仍是MRC的管理内存模式 ,在MRC管理模式中,Block处于栈区,超出作用域就会被销毁 ,如果用一个属性来全局的记录这个Block,就必须满足两个条件: 1.这个属性必须对Block强引用 2.需要把Block拷贝到堆区 要满足以上两个条件就需要使用copy修饰符

Block的循环引用

满足什么条件会出现循环应用

  • block和外部变量互相强引用导致出现循环引用,内存不能正常释放
  • :不要在Block的内部使用成员变量(_name),而要尽量使用属性,因为看不到self字段,会造成如果出现循环引用不容易发现的问题.

    :不是所有的 self. 都会出现循环引用 —— block 执行完毕就销毁,例如 UIView 的动画代码

如何解决循环引用

1
2
// 使用 __weak 修饰符,定义一个弱引用的对象
__weak typeof(self) weakSelf = self;
This post is licensed under CC BY 4.0 by the author.