block

一.关于block

block是将函数及其执行上下文封装起来的对象。

__block_impl结构体为

struct __block_impl {
  void *isa;//isa指针,所以说Block是对象
  int Flags;
  int Reserved;
  void *FuncPtr;//函数指针
};

block内部有isa指针,所以说其本质也是OC对象

二、block变量截获

1、局部变量截获 是值截获。【所以外部值改变,不影响内部已经捕获的值,block内部不能直接修改block外部的局部变量会报错,因为block内部值截获过程会生成同名变量,涉及到重名问题,所以不能改变值】
2、局部静态变量截获 是指针截获。【block内部修改值,外部变量也会改变,所以能改变值】
3、全局变量,静态全局变量截获:不截获,直接取值。【因为全局的作用域广,无需捕获,直接采取值,所以能改变值】
结论:对局部变量进行赋值操作需添加__block修饰符,而对全局变量,静态变量是不需要添加__block修饰符【__block修饰的局部变量,也是以指针形式截获,并且会生成一个新的结构体对象,__block不能修饰全局变量,会报错,不做阐述】


NSInteger num2 = 3000;

static NSInteger num4 = 300;
// __block NSInteger num6 = 30000; 不能修饰,报错
- (void)blockTest
{
NSInteger num = 30;
    
    static NSInteger num3 = 3;
    
    __block NSInteger num5 = 30000;
    
    void(^block)(void) = ^{
        
        NSLog(@"%zd",num);//局部变量-截获值-NSInteger num;
        
        NSLog(@"%zd",num2);//全局变量-未截获-直接取值
        
        NSLog(@"%zd",num3);//局部静态变量-指针形式截获- NSInteger *num2;
        
        NSLog(@"%zd",num4);//全局静态变量-未截获-直接取值
        
        NSLog(@"%zd",num5);//__block修饰变量-指针形式截获,并且生成了一个新的结构体对象:__Block_byref_num5_0 *num5;
        
        //num = 1; 不能修改局部变量-报错
        num2 = 1;
        num3 = 1;
        num4 = 1;
        num5 = 1;
    };
    
    num = 0;
    num2 = 0;
    num3 = 0;
    num4 = 0;
    num5 = 0;
   
    block();
  
    block();
}

输出打印
2021-04-23 17:11:06.072615+0800 ocProjectDemo[26928:808933] 30
2021-04-23 17:11:06.072826+0800 ocProjectDemo[26928:808933] 0
2021-04-23 17:11:06.073316+0800 ocProjectDemo[26928:808933] 0
2021-04-23 17:11:06.073806+0800 ocProjectDemo[26928:808933] 0
2021-04-23 17:11:06.074156+0800 ocProjectDemo[26928:808933] 0
2021-04-23 17:11:06.074666+0800 ocProjectDemo[26928:808933] 30
2021-04-23 17:11:06.074985+0800 ocProjectDemo[26928:808933] 1
2021-04-23 17:11:06.075397+0800 ocProjectDemo[26928:808933] 1
2021-04-23 17:11:06.075762+0800 ocProjectDemo[26928:808933] 1
2021-04-23 17:11:06.076213+0800 ocProjectDemo[26928:808933] 1

三,block的几种形式

分为全局Block(_NSConcreteGlobalBlock)、栈Block(_NSConcreteStackBlock)、堆Block(_NSConcreteMallocBlock)三种形式
其中栈Block存储在栈(stack)区,堆Block存储在堆(heap)区,全局Block存储在已初始化数据(.data)区
1、不使用外部变量的block是全局block
2、使用外部变量并且未进行copy操作的block是栈block
3、对栈block进行copy操作,就是堆block,而对全局block进行copy,仍是全局block, 对栈blockcopy之后,并不代表着栈block就消失了,【被拷贝的栈block仍然是栈block,拷贝后的栈block是堆block】左边的mallock是堆block,右边被copy的仍是栈block

四.Block与代理,通知的区别

1.代理,特点是一对一的形式,逻辑结构非常清晰。实现起来较为简单:写协议 ,设置代理这个属性, 最好在你想通知代理做事情的方法中调用即可。
当然这里面有一些细节,包括
①协议定义时,请用关键字@required,和@optional来明确代理是否必须实现某些方法
②代理的类型需用id类型,并写明要遵守的协议
③就是在调用代理方法的时候需要判断代理是否实现该方法。
2.通知,通知的优点很明显,他是一对多的形式,而且可以在任意对象之间传递,不需要二者有联系,当然他的实现和代理相比较稍微绕一点,注册,发通知,收通知。
这里面的注意点就是
①对于系统没有定义的事件监听时需要自己发通知,这是你就需要定义一个key,字符串类型,这也是通知的一个弊端,你需要拷贝到收通知的对象,避免写错一个字母而无法收通知的尴尬
②就是注册的通知中心需要手动移除,不然除了性能的问题还会有其他的问题出现,比如说一个控制器消失了之后还有因为某些事件而发出通知,造成不想要的结果。
3.block,这是苹果后来才加入的,也是目前开发比较常用的一种方式,功能比较强大,但是在理解和使用上可能需要一段时间摸索和熟悉。他的最大特点就是回调,而且回调时可以传入参数。

五.block的常用使用方法

  1. block语法


    783864-3ad5d92333756aa7.png.jpeg

2.block作为属性【常作为逆向传值,A跳往B,B回调A】

B.h声明:
@property(nonatomic, copy)void(^backBlock)(NSString* typeSring);

B.m调用:
if (self.backBlock) {
        self.backBlock(@"backBlock---block回调");   
    }

A.m回调:
xxxController * vc = [[xxxController alloc]init];
        vc.backBlock = ^(NSString * _Nonnull typeSring) {
            NSLog(@"%@", typeSring);
        };
[self presentViewController:vc animated:YES completion:nil];

3.block作为方法参数【常作为方法返回数值,并回调执行代码块】

B.h声明:
-(void)backBlock:(NSString *)str Sucess:(void(^)(NSString* typeSring))sucess Fanil:(void(^)(NSString* typeSring))fanil;

B.m 实现:
-(void)backBlock7:(NSString *)str Sucess:(int(^)(NSString* typeSring))sucess Fanil:(int(^)(NSString* typeSring))fanil{
    
     sucess([NSString stringWithFormat:@"%@---block回调",str]);
     fanil([NSString stringWithFormat:@"%@---block回调",str]);

}
A.m 回调执行:
[vc backBlock7:@"backBlock7" Sucess:^ void(NSString * _Nonnull typeSring) {
            
            NSLog(@"%@", typeSring);
  
        } Fanil:^ void(NSString * _Nonnull typeSring) {
            
            NSLog(@"%@", typeSring);
      
        }];

六. block循环引用
为何循环引用?简单来说,就是形成了,引用环,导致无法释放

B.h
@property(nonatomic, copy)void(^backBlock)(void);
B.m
   if (self.backBlock) {
        self.backBlock();
    }
    //引发循环引用  self->backBlock1->self
    self.backBlock = ^{
        NSLog(@"%@", self);
    };

七.copy,weak,assign ,strong,assign修饰blok的区别

无特殊需求,建议copy修饰blok,安全省心
1.因为block使用的weak修饰,在引入外部变量时,block存在于栈中,self又保存在block中,所以self也在栈里面,当执行完_block()之后,block就马上把自己释放掉了,从而self也一同没了,引起崩溃。
2.默认情况下,block是存档在栈中,可能被随时回收,当函数内部代码结束时,函数中的所有存储在栈区的变量都会被系统释放, 因此如果属性的block是用assign修饰时 当再次访问时就会出现野指针访问。
3.strong也不适合,强引用可能导致block无法释放

使用总结:

1.当block里面会有b类相关的参数要回调回去的时候,在引入外部变量时,block为栈block,属性用copy修饰【栈block copy 为堆block】,将其拷贝到堆里面,这样即便栈释放掉了,b类的指针也在堆中存在,能够成功的回调回去。【不引用外部变量时类型是全局block,对全局block, copy还是全局block,所以符合本文三的观点】
2.如果语法块仅仅是执行而不再回调回去了, 比如操作某个数据库,修改某个单利类的属性,发送某个通知之类的,则可以用weak来修饰。

有人会问:

为什么不能这里不都用copy呢,原因是 优化内存。 如果这个类要传入1000Block来执行,而这个类又不会马上释放掉的话,用copy是不是就拷贝了1000个在堆里面? 这样就会占用很大一部分内存,如果使用了weak将不必要的执行后就可以马上释放掉是不是就节约了很多的内存了。

参考链接:
https://www.jianshu.com/p/61aa1062a27b
https://blog.csdn.net/qq_27074387/article/details/96402464
http://blog.sina.com.cn/s/blog_799abc950102x1oi.html

你可能感兴趣的:(block)