• iOS开发系列-Block


    概述

    在iOS 4.0之后,block横空出世,它本身封装了一段代码并将这段代码当做变量,通过block()的方式进行回调。这不免让我们想到在C函数中,我们可以定义一个指向函数的指针并且调用。

    #import <Foundation/Foundation.h>
    void function(){
        NSLog(@"function执行了");
    }
    int main(int argc, const char * argv[]) {
        
        void(*funcP)(void) = function;
        // 函数指针调用函数
        funcP();
        
        return 0;
    }
    

    Block的本质就是函数指针。只要通过函数指针可以在任何时候执行函数

    Block基本使用

    Block的类型

    block也是一种数据类型,Block的类型是什么呢。
    返回值类型(^)(参数类型列表)就是Block的类型。开发中可以利用typedef定义同一种类型的Block。

    // 定义一个没有返回值没有形参的Block类型MyBlock
    typedef void(^MyBlock)(void);
    

    Block的声明定义

    声明一个block类型的变量

     返回值类型(^blockName)(参数类型列表)
    

    定义一个block

    ^返回值类型(参数列表) {
            
        };
    

    Block的定义不管有没有返回值,在定义时返回值类型可以省略。当一个block没有参数时Block定义^后面的括号也可以省略。

    // 标准的声明与定义一个block
        void (^block)() = ^void(){
            
        };
        
        // 定义省略返回值
        void (^block)() = ^(){
            
        };
        
        // block没有返回值省略^后面的括号
        void (^block)() = ^{
            
        };
    

    Block的使用场景

    监听逆向传值

    开发中我们通常使用代理来做监听并且逆向传值,其实使用Block也可以做到。给被监听着添加一个Block属性,在外界给被监听着赋值block。当被监听着内部发生了事件想通知给外界可以执行属性block。通过Block的参数将值传递出来。

    其实本质就是Block就是一个函数指针,block定义其实就是一个定义函数的实现。当执行block,就通过block指针地址找到方法实现执行函数。这个Block定义在监听者内部,当被监听者内部执行了block就等于执行了监听者内部定义的函数。

    Block的内存管理

    MRC下Block的内存管理

    在MRC中Block在内存中的位置是有多种情况,总体分为三种.
    * NSGlobalBlock
    * NSStackBlock
    * NSMallocBlock

    在MRC定义一个Block,对Block进行控制台输出发现Block默认是在全局区的。

    void (^block)(void) = ^void(){
        NSLog(@"----------------");
    };
    NSLog(@"%@", block);
    

    当Block内部访问了局部变量,Block是在栈区。如果访问外部的变量静态变量或者全局变量,Block还是保存在全区区。

    int a = 12;
    void (^block)(void) = ^void(){
        NSLog(@"----------------%d", a);
    };
    

    当一个栈区的Block通过copy后会生成新的Block,此时的Block存储在堆区。全局区的Block被copy后没有生成新的Block。

    int a = 10;
    void (^block)(void) = ^void(){
        // 访问了外界的局部变量a block就保存在栈区
        NSLog(@"----------------%d", a);
    };
        
    NSLog(@"%@", block);
    NSLog(@"%@", [block copy]);
    

    这就是为什么在MRC下,Block属性Propery会使用copy。如果使用的是retain那么block在栈区过了作用域就会释放,当调用者属性block时发生坏访问。
    如果是MRC声明了一个copy修饰的属性,建议在对象的dealloc的方法对所拥有的Block进行release。

    - (void)dealloc
    {
        [self.block release];
        
        [super dealloc];
    }
    

    ARC下Block的内存管理

    在ARC下默认定义Block同样存储在全局区,不同的是在ARC下,Block内部引用了局部变量是存储在堆区的。

    static int a = 10;
    void (^block)(void) = ^void(){
        NSLog(@"----------------%d", a); // <__NSMallocBlock__: 0x604000241860>
    };
    

    在ARC下如果声明一个Block属性property修饰符建议使用strong。如果使用的copy跟strong的作用一样用一个强指针引用着Block。但是copy内部需要做一些逻辑处理,为了性能建议使用strong。

    Block循环引用

    在FMModelVCViewController控制器中声明一个block属性。在viewDidLoad中创建一个block为属性赋值。block内部输出self

    @interface FMModelVCViewController ()
    /** block属性 */
    @property (nonatomic, strong) void(^block)(void);
    @end
    
    @implementation FMModelVCViewController
    
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        void(^block)(void) = ^{
            NSLog(@"----------------%@", self);
        };
        
        self.block = block;
    }
    

    Block会对里面的所有外部强指针变量进行强引用。上面的代码就造成了循环引用,在内存中控制器不会销毁。

    解决方案

    在Block内部访问控制器__weak修饰指针的。

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        __weak typeof(self) weakSelf = self;
        void(^block)(void) = ^{
            NSLog(@"----------------%@", weakSelf);
        };
        
        self.block = block;
    }
    

    这样Block对控制器强产生的是弱引用。

    Block中延时任务问题

    当Block中有延时操作,延时操作block中想访问外界的对象,但是通常Block为了防止循环引用使用是_weak修饰的对象指针。当Block内部的延时Block访问的weak修饰的对象也是弱引用。有可能造成当执行延时的Block时,其内部引用的外部对象已经销毁。

    #import "FMModelVCViewController.h"
    
    @interface FMModelVCViewController ()
    /** block属性 */
    @property (nonatomic, strong) void(^block)(void);
    @end
    
    @implementation FMModelVCViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        __weak typeof(self) weakSelf = self;
        void(^block)(void) = ^{
    
            // afterBlock由系统管理
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"----------------%@", weakSelf);
            });
        };
        
        self.block = block;
        
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        // 执行block
        self.block();
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    @end
    

    当点击屏幕时控制器执行了dismissViewControllerAnimated控制器方法控制器销毁,2s过后执行延时Block是输出null;

    此时的内存如下:

    解决方案

    对Block内部代码调整如下

    这样2s之后依然可以访问到控制器,当延时Block执行完毕。控制器才销毁。至于为什么通过内存图你就明白了。

  • 相关阅读:
    安装ThinkPHP
    PHP数据如何向上取整
    Bootstrap 网格系统
    一篇文章让你详细了解何为JSON
    swan.onPageNotFound
    php长连接和短连接的使用场景
    写php用什么编辑器
    php随机数原理
    php接受post传值的方法
    做网站用php还是python
  • 原文地址:https://www.cnblogs.com/CoderHong/p/8846760.html
Copyright © 2020-2023  润新知