• 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
    
  • 相关阅读:
    mysql 函数 存储过程 事件(event) job 模板
    protobuf 无proto 解码 decode 语言 java python
    mitmproxy fiddler 抓包 填坑
    android adb 常用命令
    android机器人 模拟 踩坑过程
    RabbitMQ添加新用户并支持远程访问
    Windows下RabbitMQ安装及配置
    Java mybatis mysql 常用数据类型对应关系
    easyExcel 踩坑
    linux防火墙查看状态firewall、iptable
  • 原文地址:https://www.cnblogs.com/r360/p/15772240.html
Copyright © 2020-2023  润新知