zoukankan      html  css  js  c++  java
  • iOS底层原理探索Block基本使用

    block是什么?

    block其实是一段代码块,其作用是保存一段代码块,在真正调用block的时候,才执行block里面的代码。

    在程序里面输入inlineBlock,就可以得到block的声明与定义形式:

    /**
     等号前面是block的声明;
     等号后面是block的定义;
     returnType:block声明的返回类型
     blockName:block的名字
     parameterTypes:block声明的参数类型
     parameters:block定义的参数类型及参数值
     statements:block代码块
     */
    returnType(^blockName)(parameterTypes) = ^(parameters) {
        statements
    };//末尾的;不能省略
    
    block的定义常见的有四种形式:
    1. 无参数,无返回值
    2. 无参数,有返回值
    3. 有参数,无返回值
    4. 有参数,有返回值

    我们具体看一下四种常见的形式以及各个形式的类型都长什么样:

    - (void)viewDidLoad {
        [super viewDidLoad];
        /**
         1.无参数,无返回值
         定义的时候,没有返回值可以不写()及里面的内容
         其block类型是:void(^)(void)
         */
        void(^block1)(void) = ^{
            NSLog(@"block1");
        };
        
        /**
         2.1无参数,有返回值
         其block类型是:int(^)(void)
         */
        int(^block21)(void) = ^int{
            NSLog(@"block21");
            return 21;
        };
        
        /**
         2.2无参数,有返回值
         定义的时候,返回值可以省略
         其block类型是:int(^)(void)
         */
        int(^block22)(void) = ^{
            NSLog(@"block22");
            return 22;
        };
        
        /**
         3.有参数,无返回值
         有参数的情况下,声明的参数类型必须写
         有参数的情况下,定义的参数类型和参数名必须写
         其block类型是:void(^)(int)
         */
        void(^block3)(int) = ^(int a){
            NSLog(@"block3---%d", a);
        };
        
        /**
         4.有参数,有返回值
         定义的时候,返回值int可以省略
         其block类型是:int(^)(int)
         */
        int(^block4)(int) = ^int(int a){
            NSLog(@"block4---%d", a);
            return 4;
        };
        
        /**block的调用*/
        block1();
        int a = block21();
        NSLog(@"%d", a);
        int b = block22();
        NSLog(@"%d", b);
        block3(3);
        NSLog(@"%d", block4(4));
    }
    
    运行结果:
    
    2020-03-06 15:23:18.860990+0800 test001[3456:921319] block1
    2020-03-06 15:23:18.861075+0800 test001[3456:921319] block21
    2020-03-06 15:23:18.861100+0800 test001[3456:921319] 21
    2020-03-06 15:23:18.861122+0800 test001[3456:921319] block22
    2020-03-06 15:23:18.861141+0800 test001[3456:921319] 22
    2020-03-06 15:23:18.861161+0800 test001[3456:921319] block3---3
    2020-03-06 15:23:18.861190+0800 test001[3456:921319] block4---4
    2020-03-06 15:23:18.861211+0800 test001[3456:921319] 4
    

    通过以上代码,我们可以得出一下结论:

    block在定义的时候,参数为空的时候,可以将定义里面的()以及内容都省略
    block在定义的时候,参数不为空的时候,参数值和参数名都不可以省略
    block在定义的时候,返回值不管有或者没有都可以省略

    block的声明

    在interface里面声明block的时候,有两种方法:

    typedef void(^BLOCK2)(void);
    
    @interface ViewController ()
    /**在ARC下,strong和copy修饰block都可以*/
    
    /**直接声明,按照block的声明样式写就可以*/
    @property (strong, nonatomic) void(^block1)(void);
    
    /**将BLOCK2进行定义类型,然后在定义变量block2*/
    @property (strong, nonatomic) BLOCK2 block2;
    @end
    
    block使用场景-反向传值

    我们知道,两个对象的传值有两种:正向传值反向传值
    其中,正向传值可以直接将值赋值过去完成传值动作。
    而反向传值,一般有三种方法:代理、block和通知,然后,我们介绍一下block的反向传值。

    /**
    ViewController
    */
    #import "ViewController.h"
    #import "TestViewController.h"
    @interface ViewController ()
    @end
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        TestViewController *testVc = [[TestViewController alloc] init];
        testVc.myBlock = ^(int value) {
            NSLog(@"value = %d", value);
        };
        
        [self presentViewController:testVc animated:YES completion:nil];
    }
    
    /**TestViewController*/
    @interface TestViewController : UIViewController
    @property (strong, nonatomic) void(^myBlock)(int value);
    @end
    
    #import "TestViewController.h"
    @interface TestViewController ()
    @end
    
    @implementation TestViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor redColor];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        if (self.myBlock) {
            self.myBlock(100);
        }
    }
    @end
    
    两次点击后,打印如下:
    2020-03-06 17:49:31.981064+0800 test001[3497:936661] value = 100
    

    第一次点击屏幕,会呼出TestViewController,
    第二次点击屏幕,会调用myBlock(100),其实它是做了如下操作:

    在TestViewController中myBlock(100);的调用等价于:
    if (self.myBlock) {
            value = 100;
            ^(int value) {
                NSLog(@"value = %d", value);
            }();
        }
    

    block在MRC下的内存存储地址

    首先我们要明确一点的是,block其实是一个对象,那么,block这个对象,存储在什么区呢?
    我们知道,内存分为五大区:
    栈stack(系统) | 堆malloc、heap(手动) | 静态区(全局区) | 常量区 | 方法区(程序代码区)

    block存储在哪个区呢?

    这个,需要根据项目是MRC或者ARC做具体的判断。

    在讲MRC或者ARC前,我们先了解一些基本知识点:

    ARC管理原则:
    只要一个对象没有被强指针引用,该对象就会被销毁。
    默认局部变量对象都是使用强指针引用,并存放在堆里面。
    MRC管理原则:
    MRC没有strong、weak修饰指针,局部变量对象做基本数据类型处理,基本数据类型统一放在栈区。
    MRC常用知识点:
    MRC给属性赋值的时候,一定要用set方法,不能直接访问下划线成员属性。因为,在MRC下的set方法会做一些其他事情,而直接用_成员属性就不会做这些事情。

    在MRC下,@property (retain, nonatomic) NSString *name;该句代码的set方法的实现转换为下面代码:
    - (void)setName:(NSString *)name
    {
        if (_name != name) {
            [_name release];
            _name = [name retain];
        }
    }
    

    首先,我们看下在MRC环境下block的存储位置
    将Build Setting下的Objective-C Automatic Reference Counting设置为NO即是在MRC环境下

    在这里插入图片描述

    void(^block)(void) = ^{
        NSLog(@"调用了block");
    };
    NSLog(@"%@", block);
    结果:<__NSGlobalBlock__: 0x1006a0080>
    
    int a = 3;//局部变量
    void(^block)(void) = ^{
        NSLog(@"调用了block, a = %d", a);
    };
    NSLog(@"%@", block);
    结果:<__NSStackBlock__: 0x16f971328>
    
    __block int a = 3;
    void(^block)(void) = ^{
        NSLog(@"调用了block, a = %d", a);
    };
    NSLog(@"%@", block);
    结果:<__NSStackBlock__: 0x16f935310>
    
    static int a = 3;//static修饰局部变量
    void(^block)(void) = ^{
        NSLog(@"调用了block, a = %d", a);
    };
    NSLog(@"%@", block);
    结果:<__NSGlobalBlock__: 0x1008ec080>
    
    int global = 5;
    - (void)viewDidLoad {
        [super viewDidLoad];
        void(^block)(void) = ^{
            NSLog(@"调用了block, global = %d", global);
        };
        NSLog(@"%@", block);
    }
    结果:<__NSGlobalBlock__: 0x100934080>
    
    static int global = 5;
    - (void)viewDidLoad {
        [super viewDidLoad];
        void(^block)(void) = ^{
            NSLog(@"调用了block, global = %d", global);
        };
        NSLog(@"%@", block);
    }
    结果:<__NSGlobalBlock__: 0x100be8080>
    
    __block int global = 5;//__block修饰全局变量,报错
    

    通过代码可以看出,如果使用retain修饰block,则self.block存放在栈区。栈是由系统自动控制,则在代码块{}执行完毕后,self.block将被回收,而在touchesBegan方法中还调用self.block,报野指针错误。
    然后,我们看下使用copy修饰block:

    @property (copy, nonatomic) void(^block)(void);
    
    结果:<__NSMallocBlock__: 0x28237b2d0>
    触摸点击打印:调用了block, a = 3
    
    (其他代码与上面相同)
    

    这是因为,使用copy修饰block,self.block存储在堆区,而堆内存区域是由程序员自己控制的,因此,在viewDidLoad方法执行完毕后,self.block的内存地址并没有被回收,因此在touchesBegan方法中调用self.block();没有问题。

    总结:

    在MRC下
    block属性必须使用copy,而不能使用retain修饰

    void(^block)(void) = ^{
        NSLog(@"调用了block");
    };
    NSLog(@"%@", block);
    结果:<__NSGlobalBlock__: 0x100b5c098>
    
    int a = 3;//局部变量
    void(^block)(void) = ^{
        NSLog(@"调用了block, a = %d", a);
    };
    NSLog(@"%@", block);
    结果:<__NSMallocBlock__: 0x28343ea60>
    
    __block int a = 3;
    void(^block)(void) = ^{
        NSLog(@"调用了block, a = %d", a);
    };
    NSLog(@"%@", block);
    结果:<__NSMallocBlock__: 0x2805b11d0>
    
    static int a = 3;//static修饰局部变量
    void(^block)(void) = ^{
        NSLog(@"调用了block, a = %d", a);
    };
    NSLog(@"%@", block);
    结果:<__NSGlobalBlock__: 0x100f38098>
    
    int global = 5;
    - (void)viewDidLoad {
        [super viewDidLoad];
        void(^block)(void) = ^{
            NSLog(@"调用了block, global = %d", global);
        };
        NSLog(@"%@", block);
    }
    结果:<__NSGlobalBlock__: 0x100564098>
    
    static int global = 5;
    - (void)viewDidLoad {
        [super viewDidLoad];
        void(^block)(void) = ^{
            NSLog(@"调用了block, global = %d", global);
        };
        NSLog(@"%@", block);
    }
    结果:<__NSGlobalBlock__: 0x100b54098>
    
    __block int global = 5;//__block修饰全局变量,报错
    

    总结:

    在ARC下
    block本身是存储在全局区;
    如果block引用了外部局部变量,或者引用了被__block修饰的外部局部变量,则存放在堆区。
    被__block修饰的block还是局部变量;
    被static修饰的局部变量,改变局部变量的声明周期。
    在ARC中使用copy修饰block

    @interface ViewController ()
    @property (copy, nonatomic) void(^block)(void);
    @end
    
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        int a = 3;
        void(^block)(void) = ^{
            NSLog(@"调用了block, a = %d", a);
        };
        self.block = block;
        NSLog(@"%@", self.block);
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        self.block();
    }
    
    结果:<__NSMallocBlock__: 0x28065c0f0>
    触摸点击打印:调用了block, a = 3
    

    在ARC中使用strong修饰block

    @property (strong, nonatomic) void(^block)(void);
    
    结果:<__NSMallocBlock__: 0x2802b5ef0>
    触摸点击打印:调用了block, a = 3
    

    在ARC中使用weak修饰block

    @property (weak, nonatomic) void(^block)(void);
    
    结果: <__NSMallocBlock__: 0x282cdb3c0>
    触摸点击崩溃,报EXC_BAD_INSTRUCTION错误
    

    在ARC中使用assign修饰block

    在ARC下
    block属性可以使用copy或者strong修饰,不能使用weak或者assign修饰
    通过ARC管理机制我们知道,如果没有任何强指针引用,则对象会被清空。所以,用weak或者assign没有对block进行强指针引用,因此,在viewDidLoad方法执行完毕后,block就被清空,再次使用self.block会造成野指针错误。
    在ARC下,string和block用copy还是strong?

    其实两个都可以使用,但是还是建议使用strong。
    string使用copy修饰,只是浅拷贝,并没有建立新的对象,所以,strong就可以满足。
    block使用copy修饰,也没有新的对象创建,所以,strong就可以满足。
    而,strong和copy不一样的地方在于,使用copy修饰的属性,在set方法中,会有一些列的copy操作,而strong并不需要,从性能上说,strong高一些。

    Block的循环引用注意事项

    block会对里面所有的外部变量对象进行强引用。

    int a = 3;//局部变量
    void(^block)(void) = ^{
        NSLog(@"调用了block, a = %d", a);
    };
    a = 4;
    block();
    结果:调用了block, a = 3
    
    static int a = 3;//static修饰局部变量
    结果:调用了block, a = 4
    
    __block int a = 3;//__block修饰局部变量
    结果:调用了block, a = 4
    
    int a = 3;//全局变量
    结果:调用了block, a = 4
    
    static int a = 3;//static修饰全局变量
    结果:调用了block, a = 4
    

    总结

    block调用局部变量是值传递;
    使用static或者__block修饰的局部变量是指针传递;
    全局变量和使用static修饰的全局变量,block没有捕获全局变量,因此,在block内部可以修改全局变量
    上面的总结第三点其实是不对的,为什么呢?

    为什么是这样的结果呢???

    想了解更多的小伙伴,可以看这两篇文章,为你答疑解惑。
    iOS之Block本质(一)
    iOS之Block本质(二)

    block作为参数

    block作为参数使用,在UIView的动画里面很常见,例如:

    [UIView animateWithDuration:3.0 animations:^{   
    }];
    
    其方法是:
    + (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
    

    (void (^)(void))是一个参数为void,返回值为void的block类型,animations是block变量名。
    可以看出,block作为参数时,使用 (block类型)block变量名 的形式定义。

    我们什么时候让block作为参数使用呢?

    这个可以想下,我们什么时候使用动画。
    在3.0内,执行animations里面的内容,什么时候执行这个方法,是由UIView调用animateWithDuration方法决定的,至于里面要执行的什么动画,则是一个block,是一个代码块,是可以由程序员自己定义自己写的。

    换言之,block代码块里面的内容是程序员保存的一端代码,写完并没有立马执行。什么时候执行呢?是由UIView调用animateWithDuration方法执行的。

    举一个例子:

    @interface Calculator : NSObject
    @property (assign, nonatomic) int result;
    //定义一个方法,参数类型是int(^)(int),参数名是block
    - (void)jisuan:(int(^)(int))block;
    @end
    
    @implementation Calculator
    //方法的实现
    - (void)jisuan:(int(^)(int result))block
    {
        if (block) {
            //调用block,并将block调用的结果赋值给result
            _result = block(_result);
        }
    }
    @end
    
    Calculator *calculator = [[Calculator alloc] init];
    [calculator jisuan:^int(int result) {//block的参数名不可省略,block的返回值类型可以省略(第一个int)
        result += 5;
        result *= 2;
        return result;
    }];
    
    NSLog(@"%d", calculator.result);
    结果:10
    

    这个例子中,jisuan:方法里面的参数是一个block,当调用这个方法的时候,调用的block,执行block里面的代码。

    block作为返回值

    block作为返回值的经典代表就是Masonry三方框架,基本上整个框架都是使用的Block作为返回值。

    拿一个简单的Masonry布局代码进行分析:

    [thirdView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.equalTo(bottomView);
            make.top.equalTo(_secondView.mas_bottom);
            make.height.equalTo(@70);
    }];
    

    make.left.right.equalTo(bottomView);
    代码中,从前往后执行,首先是make.left,该语法是一个get方法,自动寻找是否有left方法,并返回对象本身(类型MASConstraintMaker),make.left.right以及make.left.right.equalTo都是返回对象本身(类型MASConstraintMaker),则最后是(MASConstraintMaker类型)(bottomView)。其实iMASConstraintMaker类型是一个block类型,最后的调用是对该block进行了调用,参数是bottomView。

    来个简单的block作为返回值的例子:

    - (void)viewDidLoad {
        [super viewDidLoad];
        self.test();
    }
    - (void(^)(void))test{
        //方法一:定义一个变量名为block的blocke,并返回
        void(^block)(void) = ^{
            NSLog(@"调用test函数返回block");
        };
        return block;
        
        //方法二:直接返回一个block
        return ^{
            NSLog(@"调用test函数返回block");
        };
    }
    
    结果:调用test函数返回block
    

    接下来,我们做一个简单的连续进行加法计算的例子

    @interface Calculator : NSObject
    @property (assign, nonatomic) int result;
    //定义一个方法,返回值是其本身
    - (Calculator *)jisuanAdd:(int)value;
    @end
    
    @implementation Calculator
    //方法的实现
    - (Calculator *)jisuanAdd:(int)value
    {
        self.result += value;
        return self;
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        Calculator *calculator = [[Calculator alloc] init];
        [[[calculator jisuanAdd:2] jisuanAdd:5] jisuanAdd:3];
        NSLog(@"%d", calculator.result);
    }
    
    结果:10
    

    通过上面的例子,我们可以实现连续进行加法的计算,我们对例子进行改进,使其可以跟Masonry一样,进行链式编程。

    @interface Calculator : NSObject
    @property (assign, nonatomic) int result;
    //定义一个方法,返回值是一个block,该block是一个参数为int,返回值为Calculator的类型
    - (Calculator *(^)(int))jisuanAdd;
    @end
    
    @implementation Calculator
    //方法的实现
    - (Calculator *(^)(int))jisuanAdd
    {
    	//返回一个block
        return ^(int value){
            self.result += value;
            return self;
        };
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        Calculator *calculator = [[Calculator alloc] init];
        calculator.jisuanAdd(2).jisuanAdd(5).jisuanAdd(3);
        NSLog(@"%d", calculator.result);
    }
    
    结果:10
    
  • 相关阅读:
    线程和进程
    Java多线程实现(四种方法)
    Java中的锁
    synchronized和java.util.concurrent.locks.Lock
    Lock的实现类ReentrantLock&Condition类的await/signal/signalAll(生产者消费者场景)
    synchronized&Object类的wait/notify/notifyAll(生产者消费者场景)
    SQL语句优化
    面试
    数据库三大范式
    设计模式之JDK动态代理源码分析
  • 原文地址:https://www.cnblogs.com/r360/p/15772240.html
Copyright © 2011-2022 走看看