关于OC中的block介绍

作者: 瓷月亮 | 来源:发表于2017-08-27 17:51 被阅读10次

    摘要:OC中的block和swift中的闭包在iOS开发中很常见,使用场景也非常多,例如块动画.GCD的回调.迭代器以及逆向传值等.下面将从相关原理. 逆向传值. 内存管理和循环引用几个方面详细介绍OC中的block.


    一.Block的内存管理

        1.不引用任何外部变量的block,保存在全局区(__NSGlobalBlock__),代码的执行效率高,但这种情况在实际开发中几乎是不存在的.eg:

    - (void)blockDemo1 {

        void(^myBlock)() = ^ {

         NSLog(@"hello world");

         };

         NSLog(@"%@", myBlock);

    }

      2.引用外部变量的block.在MRC环境下,block默认存储在栈区,出了作用域就会弹栈.要想作为能够被全局访问的属性,就要用copy修饰,因为在对全局属性的block实例化的时候,走setter方法,用copy修饰系统会将block从栈区拷贝到堆区,实现全局共享.目前开发基本都在ARC环境下,block本来就在堆区,做为全局属性既可以用strong修饰,但apple建议使用copy修饰.

    3.在MRC和ARC下,block做为属性和成员变量的区别:

           MRC下,block默认存储在栈区.做为属性赋值时,系统会将其拷贝到堆区,引用计数器也会+1.而对成员变量赋值时,仅仅是计数器+1,不会进行copy操作.

            ARC下,block无论作为属性还是成员变量都是强引用.赋值时都会copy.

    二.Block和控制器的循环引用

    1.控制器没有引用block,而block引用了控制器,示例代码:

    - (void)viewDidLoad {   

     [super viewDidLoad];

    // 定义block

    void (^block)() = ^ {

    NSLog(@"%@",self.view);   

     };

    // 调用block

    block();

    }

    在控制器的dealloc方法中打印输出,验证控制器的销毁:

    - (void)dealloc {

    NSLog(@"%s", __FUNCTION__);

    }

    运行结果:控制器的dealloc方法被触发,说明没有循环引用.因为block执行完毕后会释放,从而释放对self的引用.

    2.将block定义为控制器的属性,block内部又引用了self(ViewController),示例代码:

    *定义属性

    @property (nonatomic,copy) void (^demoBlock)();

    *在 viewDidLoad 中记录 block

    - (void)viewDidLoad {    

    [super  viewDidLoad];

    void (^block)() = ^ {

    NSLog(@"%@",self.view);    

    };

    // 记录 block

    self.demoBlock= block;

    }

            同样,在控制器的dealloc方法中打印输出,验证控制器的销毁情况.

    运行结果:dealloc方法没有被触发,说明发生了循环引用.因为控制器引用了block,block内部又引用了self.相互强引用,造成了循环引用.

    解决方案:使用__weak修饰符,定义一个弱引用的对象,示例代码:

    - (void)viewDidLoad {    

    [super viewDidLoad];

    // 把self临时定义成弱引用

    __weak typeof(self) weakSelf = self;

    void (^block)() = ^ {

    NSLog(@"%@",weakSelf.view);   

    };

    // 记录 block

    self.demoBlock= block;

    }

           特别要注意的时,在使用block时,如果block内部引用了self,同时使用了属性记录了block,要格外注意是否出现循环引用.

           另外,不是所有的self,都会出现循环引用,像UIView的动画代码块在执行完就销毁.

            再就是ARC下,成员变量也是被控制器对象强引用,如果在block内部引用了成员变量,也相当于间接强引用了控制器,那么要注意这个时候,控制器有没有也对block形成强引用.示例代码:

    @implementation ViewController {

    NSMutableArray* _arrayM;

    }

    - (void)viewDidLoad {   

     [super viewDidLoad];    

    __weak typeof(self) weakSelf = self;

    void(^block)() = ^ {

    // 循环引用点在 `_arrayM`

    NSLog(@"%@ %@", weakSelf.view, _arrayM);   

     };

    // 记录 block

    self.demoBlock= block;

    }

    三.block的回调

        block的回调功能是GCD诞生的基础.显而易见,它在反向传值中的使用非常广泛.在iOS开发中,block和代理都能实现反向传值的功能,那么它们有什么区别呢?

    block用法分为四步:

    被调用方:

    定义block属性 ->  调用block

    调用方: 

    初始化block -> 在需要的时候执行block(前提是block已经初始化,否则运行时会crash).

    代理的实现步骤有七步:

    被调用方:

    定义协议 -> 声明代理方法 ->定义代理属性 ->需要的时候判断代理对象是否能响应代理方法,能的情况下进行调用

    调用方:

    遵守协议 -> 成为代理对象 ->实现代理方法

           另外,block的所有的代码写在一起,可读性更好,使用代理的话,代码相对分散.但是block的一个回调对应一个属性,属性定义在头文件中,容易和其他属性混在一起.而代理设计模式通过协议预先定好代理方法,更加严谨.尤其是在协议方法很多的时候,使用协议更加直观清晰.

    四.关于block使用的补充

    1.当block引用外部的变量时,会对外部变量进行一次拷贝,对外部变量的真实值不会造成影响;

    2.如果要在block内部修改外部的局部变量,需要使用__block修饰这个局部变量.在block内部使用了这个变量后,这个变量的地址在后续的使用中都在堆区.

            可以这样理解,block内部不能修改外部局部变量的原因是,block一般作为回调使用,经常性的需要传递到别的类中,局部变量出了block的作用域就会被销毁,为了让局部的变量不销毁,就用__block来进行标记.

    相关文章

      网友评论

        本文标题:关于OC中的block介绍

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