iOS开发基础综合类

关于OC中的block介绍

2017-08-27  本文已影响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来进行标记.

上一篇 下一篇

猜你喜欢

热点阅读