美文网首页#iOS#HeminWoniOS开发
iOS中block的使用详解

iOS中block的使用详解

作者: eightzg | 来源:发表于2016-07-25 23:37 被阅读1371次

    block的声明和简单使用

    • 苹果官方文档声明,block是objc对象。
    block的定义方式
    • 无返回值无参数的block
    void(^block1)() = ^{
        NSLog(@"调用了block1");
    };
    
    • 无返回值有参数的block,参数的类型和传入参数的值不能省略,即int a不能省略
    void(^block2)(int) = ^(int a){
           NSLog(@"调用了block2");     
    };
    
    • 有返回值的block,返回值的类型可以省略,要是有参数,参数的类型和传入参数的值不能省略。
    int(^block3)() = ^int{
        return 3;
    };
    
    
    block定义总结

    总结:block的定义,不管block有没有返回值,block的实现部分都可以省略;若block有参数,则参数类型和传入参数的值不能省略。

    • 小tip:若初学者觉得block格式奇怪,记不住,可以输入inline就可以出现block,如图:
    Paste_Image.png

    block使用场景之----代理传值

    在开发中控制器逆传传值最常用的就是用代理的方法,然而用block也可以实现逆传传值,而且更加简单方便。

    • 场景:假如在窗口的根控制器ViewController中点击屏幕的view,模态弹出一个窗口ModalViewController,再点击ModalViewController的view销毁ModalViewController,同时将想要传的值传给ViewController。

    • 在ViewController中的- touchesBegan方法中,进行到ModalViewController的跳转,同时给ModalViewController的block属性声明,注意!这里并没有调用

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        ModalViewController *modalVc = [[ModalViewController alloc] init];
        modalVc.view.backgroundColor = [UIColor brownColor];
        modalVc.block = ^(NSString *value) {
          
            NSLog(@"%@",value);
        };
        
        // 跳转
        [self presentViewController:modalVc animated:YES completion:nil];
    }
    
    • 在ModalViewController的.h文件中声明一个block属性,这个block属性没有返回值,接收一个字符串类型的参数
    @property (nonatomic, strong) void(^block)(NSString *value);
    
    • 在ModalViewController的.m文件中,点击控制器的view进行block的调用,此时就会将字符串中的值传给ViewController。注意if语句的判断,否则block不存在的时候,会报坏内存访问错误
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        if (_block) {
            _block(@"我是萌萌哒ModalViewController");
        }
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    
    Paste_Image.png
    block的内存管理
    • block在MRC环境和ARC环境的内存管理不同

    MRC

    • 首先,在项目的building settings里面将环境改成MRC的环境
    Paste_Image.png
    • block会根据其内部引用的变量的类型不同而在不同的存储空间
    • block可以存在全局区,栈区,堆区(__NSGlobalBlock____NSStackBlock__,__NSMallocBlock__)。
      • 若block中引用外部的局部变量,则打印结果是__NSStackBlock__,说明block存储在栈区
    - (void)viewDidLoad {
        [super viewDidLoad];
        //局部变量a
        int a = 3;
        void(^block)() = ^{
            NSLog(@"调用block %d",a);
        };
        NSLog(@"%@",block);
    }
    
    Paste_Image.png
    • 若block中的变量的全局变量后者静态变量,则打印结果是__NSGlobalBlock__,说明block存储在全局区
    //全局变量a
    int a = 3;
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        void(^block)() = ^{
            NSLog(@"调用block %d",a);
        };
        NSLog(@"%@",block);
    }
    
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //静态变量a
        static int a = 3;
        void(^block)() = ^{
            NSLog(@"调用block %d",a);
        };
        NSLog(@"%@",block);
    }
    
    Paste_Image.png
    • 那么block什么时候保存在堆区呢?
    • 当block属性被copy修饰的时候放在堆区,打印结果为__NSMallocBlock __
    //定义copy修饰的block属性
    @property (nonatomic, copy) void(^block)();
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        int a = 3;
        void(^block)() = ^{
            NSLog(@"调用block%d",a);
        };
        self.block = block;
        NSLog(@"%@",self.block);
    }
    
    • 假如我们尝试用retain修饰block,会报这样的警告,同时运行的结果是保存在栈区。
    Paste_Image.png Paste_Image.png
    总结
    只要block没有引用外部局部变量,block放在全局区
    只要Block引用外部局部变量,block放在栈区.
    block只能使用copy,不能使用retain,使用retain,block还是在栈区
    

    ARC

    • 若block中的变量的全局变量后者静态变量,则打印结果仍然是__NSGlobalBlock__,block仍然存储在全局区
    • 若block中引用外部的局部变量,则打印结果是__NSMallocBlock__,说明block存储在堆区,和MRC情况不同。
    • 对于ARC下的block属性用strong和copy修饰都是一样的,但是copy的底层会让setter方法多一层对可变不可变属性的判断,浪费资源,所以在ARC的情况下用strong修饰block,同理NSString属性修饰也用strong。
    block的内存管理总结
     总结:block里面引用全局变量或者静态变量,放在全局区__NSGlobalBlock__
     
     *  MRC情况:
     *  block里面引用局部变量,存放在栈区__NSStackBlock__
     *  MRC中修饰block只能用copy,不能用retain,因为retain修饰block还在栈中,若block是局部变量,不能包住block的命,再次访问会造成坏内存访问。
     
     *  ARC情况:
     *  block里面引用局部变量,存放在堆区__NSMallocBlock__
     *  block在ARC情况下用strong修饰
     *  对于NSString和block在ARC尽量用strong,因为copy底层的setter方法会对新对象做一次copy操作,还要判断是不是不可变对象,浪费性能
     */
    
    block的循环引用
    • block会对内部的所有的强指针变量都强引用一次,这就会造成block的循环引用,造成内存泄露。下面看例子:

    在ViewController中以modal的方式展示ModalViewController,在ModalViewController中调用dismiss方法销毁控制器。则在dealloc方法中会打印ModalViewController被销毁。

    然而若定义一个block属性,并在block的实现中做如下操作:

    @property (nonatomic, strong) void(^block)();
    
    _block = ^{
        NSLog(@"%@",self);
    };
    

    此时当调用ModalViewController的dismiss方法的时候不会调用dealloc方法中的打印语句,说明ModalViewController没有被真正的销毁。因为ModalViewController强引用一个block属性,block会对内部的强指针self进行一次强引用。所以造成循环引用

    • 改进方法是将self改成弱指针,这样就不会造成循环引用
    __weak typeof(self) weakSelf = self;
    _block = ^{
        NSLog(@"%@",weakSelf);   
    };
    

    block的变量传递

    • 请看这样一段代码,大家猜一猜打印的结果是3还是5?
    /***************  1  **************/
    - (void)viewDidLoad {
        [super viewDidLoad];    
        //普通局部变量a
        int a = 3;
        void(^block)() = ^{
            NSLog(@"%d",a);  
        };
        a = 5;
        block();
    }
    

    如果这样呢?

    /***************  2  **************/
    - (void)viewDidLoad {
        [super viewDidLoad];    
        //静态变量a
        static int a = 3;
        void(^block)() = ^{
            NSLog(@"%d",a);  
        };
        a = 5;
        block();
    }
    

    这样呢?

    /***************  3  **************/
    //全局变量a
    int a = 3;
    - (void)viewDidLoad {
        [super viewDidLoad];    
        void(^block)() = ^{
            NSLog(@"%d",a);  
        };
        a = 5;
        block();
    }
    

    这样呢?

    /***************  4  **************/
    - (void)viewDidLoad {
        [super viewDidLoad];    
        //用__block修饰的局部变量a
        __block int a = 3;
        void(^block)() = ^{
            NSLog(@"%d",a);  
        };
        a = 5;
        block();
    }
    

    实际编译可以得出结果,除了1的打印结果是3,剩下的都是5。那么可以总结出结论如下:

    * 如果block中是普通的局部变量,是值传递,即在局部变量声明的那一刻就把局部变量的值放到block中了,之后在修改不会造成影响
    * 如果是静态变量,全局变量,__block修饰的变量,block都是指针传递。指针传递的值可以修改。
    

    相关文章

      网友评论

        本文标题:iOS中block的使用详解

        本文链接:https://www.haomeiwen.com/subject/dwwgjttx.html