恩美第二个APP项目将来跳槽用iOS 知识点

iOS block 捕获外部变量以及注意点

2017-03-30  本文已影响1833人  雪_晟

参考文章:深入研究Block捕获外部变量和__block实现原理
做一些简单的总结说明:

(1)对于四种非对象变量:

首先: Block会捕获哪些变量?如果Block外面还有很多自动变量静态变量,等等,这些变量在Block里面并不会被使用到。那么这些变量并不会被Block捕获进来,也就是说并不会在构造函数里面传入它们的值。Block捕获外部变量仅仅只捕获Block闭包里面会用到的值,其他用不到的值,它并不会去捕获。

其次:全局变量静态全局变量可以在Block内值被修改是为什么呢?全局变量静态全局变量在执行Block语法的时候,它们被Block捕获进去,这一点很好理解,因为是全局的,作用域很广,所以Block捕获了它们进去之后,在Block里面进行++操作,Block结束之后,它们的值依旧可以得以保存下来。

然后:对于静态变量Block是如何捕获的呢?静态变量传递给Block是内存地址值,所以能在Block里面直接改变值。在执行Block语法的时候,Block语法表达式所使用的静态变量的地址是被保存进了Block的结构体实例中,也就是Block自身中。所以能够在Block 内部修改静态变量的值。

最后:为什么自动变量无法在Block内部修改值呢?类似静态变量,自动变量也是在执行Block语法的时候,被block捕获成为Block的结构体实例中,但是Block仅仅捕获了val的值,并没有捕获val的内存地址,所以在Block内部是无法修改自动变量的值。OC可能是基于这一点,在编译的层面就防止开发者可能犯的错误,因为自动变量没法在Block中改变外部变量的值,所以编译过程中就报编译错误。错误:Variable is not assignable(missing __block type specifier)
后面会说明。

总结一下在Block中改变变量值有2种方式,一是传递内存地址指针到Block中,二是改变存储区方式(__block)。

1 传递内存地址

对于对象变量,在被Block捕获后,在Block的结构体实例变量会增加一个指针,所以传递的是指针,所以成功改变了变量的值。

2 __block 改变存储方式。

_block修饰自动变量后,_block的变量也被转化成了一个结构体:__Block_byref_i_0,这个结构体有5个成员变量。

struct __Block_byref_i_0 {
  void *__isa;   指针
__Block_byref_i_0 *__forwarding; 指向自身类型的__forwarding指针
 int __flags; 标记flag
 int __size;大小
 int i; 变量值
};

MRC环境下,只有copy,_block才会被复制到堆上,否则,_block一直都在栈上,block也只是 _NSStackBlock,这个时候_forwarding指针就只指向自己了。
ARC环境下,一旦Block赋值就会触发copy,_block就会copy到堆上,Block也是_NSMallocBlock。ARC环境下也是存在_NSStackBlock的时候,这种情况下,_block就在栈上

在ARC环境下,Block也是存在__NSStackBlock的时候的,平时见到最多的是_NSConcreteMallocBlock,是因为我们会对Block有赋值操作,所以ARC下,block 类型通过=进行传递时,会导致调用objc_retainBlock->_Block_copy->_Block_copy_internal方法链。并导致 __NSStackBlock__ 类型的 block 转换为 __NSMallocBlock__ 类型

_forwarding指针初始化传递的是自己的地址,在执行Block的时候,堆上的Block会持有对象。 当我们把Block复制到堆上,堆上的Block也会持有_block.当Block释放的时候,_block没有被任何对象引用,也会被释放销毁。堆上的_forwarding指针也指向自己,只不过一个指针是_NSConcreteStackBlock,一个是_NSConcreteMallocBlock,两份_block ,栈上的_forwarding指针指向堆区的block,堆区的_forwarding指针指向原来的自己所以这样不管_block怎么复制到堆上,还是在栈上,都可以通过(i->__forwarding->i)来访问到变量值。

Block 分类

OC中,一般Block就分为以下3种,_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock。
先来说明一下3者的区别。

weakSelf StrongSelf 的使用

解决循环应用的问题一定要分析清楚哪里出现了循环引用,只需要把其中一环加上weakSelf这类似的宏,就可以解决循环引用。_weak的实现原理,在原对象释放之后,_weak对象就会变成null,防止野指针。所以就输出了null了。

那么我们怎么才能在weakSelf之后,block里面还能继续使用weakSelf之后的对象呢?

究其根本原因就是weakSelf之后,无法控制什么时候会被释放,为了保证在block内不会被释放,需要添加_strong。

在block里面使用的_strong修饰的weakSelf是为了在函数生命周期中防止self提前释放。strongSelf是一个自动变量当block执行完毕就会释放自动变量strongSelf不会对self进行一直进行强引用。

 __weak typeof(student) weakSelf = student;

    student.study = ^{
        __strong typeof(student) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"my name is = %@",strongSelf.name);
        });

    };

_block 与_weak 的区别

1._block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
2._weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
3._block对象可以在block中被重新赋值,_weak不可以。

上一篇 下一篇

猜你喜欢

热点阅读