iOS block分析:使用方式与储存机制,变量捕获机制

来源:互联网 发布:诺可可网络 编辑:程序博客网 时间:2024/06/06 00:18

1 什么是block

    对象封装数据与行为,但有时单个行为的对象化会给系统设计和实现上带来极大的便利。Blocks是C C++ OC的语言特性,把特定行为简单对象化。

在OC中,blocks可以捕获声明代码段内的值类型(值类型、指针),并通过__block存储修饰符进一步增强了block的功能:对上下文作出修改。

2 局部变量

2.1  在ARC下:如果block捕获了值,储存在堆中,否则在代码区。

    void (^myblock)() = ^() {  };

    NSLog(@"%@", myblock);

输出:<__NSGlobalBlock__: 0x1000882d0>

    __block int j = 0;

    void (^myblock)() = ^() {

        NSLog(@"%d", j++);

    };

    NSLog(@"%@", myblock);

输出:<__NSMallocBlock__: 0x17005b690>

2.2 在MRC下上述代码段分别输出:

<__NSGlobalBlock__: 0x1000882d0>

<__NSStackBlock__: 0x16fd3e0b0>


3  属性block

3.1 ARC,有值捕获行为存储在堆上,否则在代码去。同本地block。

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

@property (nonatomic,strong)void (^strongB) ();

@property (nonatomic,assign)void (^assignB) ();

@property (nonatomic,weak)void (^weakB) ();

      __blockint i =9;

    //////////////

    void (^lcopyB)() = ^() {

        NSLog(@"%d", i++);

    };

    NSLog(@"copy %@", lcopyB);

    self.copyB = lcopyB;

    NSLog(@"copy %@",self.copyB);

    ///////////////

    void (^lstrongB)() = ^() {

        NSLog(@"%d", i++);

    };

    NSLog(@"strong %@", lstrongB);

    self.strongB = lstrongB;

    NSLog(@"strong %@",self.strongB);

    /////////////////

    void (^lassignB)() = ^() {

        NSLog(@"%d", i++);

    };

    NSLog(@"assign %@", lassignB);

    self.assignB = lassignB;

    NSLog(@"assign %@",self.assignB);

    ////////////////////////////////////////////

    void (^lweakB)() = ^() {

        NSLog(@"%d", i++);

    };

    NSLog(@"assign %@", lweakB);

    self.weakB = lweakB;

    NSLog(@"assign %@",self.weakB);

输出:全都是堆上block 

 <__NSMallocBlock__: 0x1700581b0>

去除对外部变量的引用,输出 全都是全局block类型

<__NSGlobalBlock__: 0x1000e0320>


3.2 MRC,发现只有在引用外部变量时,copy与strong属性block分配在堆上,并出现了block 的拷贝。其他遵循本地block规则。

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

@property (nonatomic,strong)void (^strongB) ();

@property (nonatomic,assign)void (^assignB) ();

    __block int i = 9;   

    ///////////////////////////////////

    void (^lcopyB)() = ^() {

        NSLog(@"%d", i++);

    };

    NSLog(@"copy %@", lcopyB);

    self.copyB = lcopyB;

    NSLog(@"copy %@",self.copyB);

    ///////////////////////////////////

    void (^lstrongB)() = ^() {

        NSLog(@"%d", i++);

    };

    NSLog(@"strong %@", lstrongB);

    self.strongB = lstrongB;

    NSLog(@"strong %@",self.strongB);

    ////////////////////////////////////////////

    void (^lassignB)() = ^() {

        NSLog(@"%d", i++);

    };

    NSLog(@"assign %@", lassignB);

    self.assignB = lassignB;

   NSLog(@"assign %@",self.assignB);

输出:

copy <__NSStackBlock__: 0x16fdae0d0>

copy <__NSMallocBlock__: 0x174058120>

strong <__NSStackBlock__: 0x16fdae090>

strong <__NSMallocBlock__: 0x1740580c0>

assign <__NSStackBlock__: 0x16fdae060>

assign <__NSStackBlock__: 0x16fdae060>

去除对外部变量的引用,输出:

copy <__NSGlobalBlock__: 0x10008c2b0>

copy <__NSGlobalBlock__: 0x10008c2b0>

strong <__NSGlobalBlock__: 0x10008c2f0>

strong <__NSGlobalBlock__: 0x10008c2f0>

assign <__NSGlobalBlock__: 0x10008c330>

assign <__NSGlobalBlock__: 0x10008c330>


4  把block放入队列中时,其类型是NSMallocBlock,无法给出这样简单的测试并打印出直观的结果的。但是可以从以上结论给出答案。

 当dispatch很多block到队列中时,可观察到堆使用内存的激增,入队后block一定是放在堆上。

代码区是保存静态代码的,不保存运行中变化的数据或是代码。

栈区是保存运行时状态的,其上的所有都是当前有效。即使两次入栈出栈完全相同的东西,对运行环境来说意义也可能是完全不同的。

代码区主静,栈区主动,他们都没有管理应用复杂状态的能力。只有堆区完全受控于应用,服务于应用逻辑,管理复杂的集合状态能。

代码区逻辑在堆区状态数据的驱动下,以栈为工具,有序、持续地推进程序的运行,系统的演化。


5 小结 

在ARC下,无论是本地还是属性block,引用外部变量的block全都储存在堆上;否则都在全局区。

在MRC下, 引用外部变量的block,在属性为strong或是copy时,储存在堆上;不引用变量在全局区;引用外部变量在栈区。

在ARC下,block类型只有一个直接创建在堆上的副本;

在MRC下,有引用外部变量时,strong和copy型的block存在栈上副本和堆上副本,使用栈上副本的多次堆上strong和copy型的block赋值导致多个堆上副本的存在。

建议使用ARC,杜绝栈上副本的存在和堆上过个栈上副本的拷贝,增加编程的灵活性。


6 block 数据结构、block与虚拟内存 、block捕获值原理

6.1 block在OC中的实现结构为

struct Block_layout {

        void *isa;

        int flags;

        int reserved;

        void (*invoke)(void *, ...);

        struct Block_descriptor *descriptor;   /* Imported variables. */

    };

    struct Block_descriptor {

        unsignedlongint reserved;

        unsignedlongint size;

        void (*copy)(void *dst,void *src);

        void (*dispose)(void *);

    };

void * isa  这是c扩展为OC面向对象的一个标志。详细的分析请参考:http://www.cocoachina.com/ios/20161103/17936.html

6.2 block与虚拟内存机制

在上篇文章中提到,过多的block入队会引发内存错误、程序崩溃,而不会启用虚拟内存机制。但是过多的创建与销毁普通对象并不会导致类似的错误,实验表明在一定的范围内虚拟内存机制会你补内存的不足。

在运行时看来,block一方面是数据,另一方面含有可执行代码,具有数据与代码的二重性,是极其危险的。如果允许虚拟内存的使用,那么在block的磁盘读写期间,代码很容易被攻击。系统要求只有纯代码段的和纯数据段才可以使用虚拟内存机制。

6.3 block捕获值原理

参数的传递向来只有值与地址两种方式,通过地址的传递我们可以修改对应内存的值。如果把地址看做是要修改的值那么地址与值得传递方式就可以获得统一:在block中都是不可以修改的。在block中,想要对值类型做出修改,只能通过__block储存类型,通过__block修饰符,我们获得了对基础类型与对象等符合类型的任意修改权限。如对block使用__block修饰符:(关于__block后发生了什么请参考:http://www.2cto.com/kf/201408/326609.html )

      __block void (^block_b)(void);

    block_b = ^() {

        NSLog(@"This is a block_b");

    };

    void (^simpleBlock)(void);

    simpleBlock = ^{

        block_b = ^{

            NSLog(@"This is a cahnged block_b");

        };

        NSLog(@"%@", block_b);

    };

    block_b();

    NSLog(@"%@", block_b);

    simpleBlock();

    NSLog(@"%@", block_b);

    block_b();

输出:

This is a block_b

<__NSGlobalBlock__: 0x10006c340>

<__NSGlobalBlock__: 0x10006c3c0>

<__NSGlobalBlock__: 0x10006c3c0>

This is a cahnged block_b

创建wjf.c文件

void wjf()

{

__block int i =0;

    __blockvoid (^block_b)(void);

    block_b = ^() {

    };

    void (^simpleBlock)(void);

    simpleBlock = ^{

        block_b = ^{

        };

    };

}

 转换为c++:   clang -rewrite-objc wjf.c 可见 wjf.c++文件生成,共600多行。涉及到外部不了引用的代码如下:

struct __Block_byref_i_0 {

  void *__isa;

__Block_byref_i_0 *__forwarding;

 int __flags;

 int __size;

 int i;

};

struct __Block_byref_block_b_1 {

  void *__isa;

__Block_byref_block_b_1 *__forwarding;

 int __flags;

 int __size;

 void (*__Block_byref_id_object_copy)(void*,void*);

 void (*__Block_byref_id_object_dispose)(void*);

 void (*block_b)();

};

可见,编译器对__block修饰的int与^类型的变量都生成了一个结构体。三个block的实现转换结构如下:

struct __wjf_block_impl_2 {

  struct __block_impl impl;

  struct __wjf_block_desc_2* Desc;

  __Block_byref_block_b_1 *block_b; // by ref

  __wjf_block_impl_2(void *fp,struct __wjf_block_desc_2 *desc, __Block_byref_block_b_1 *_block_b,int flags=0) : block_b(_block_b->__forwarding) {

    impl.isa = &_NSConcreteStackBlock;

    impl.Flags = flags;

    impl.FuncPtr = fp;

    Desc = desc;

  }

};

struct __wjf_block_impl_1 {

  struct __block_impl impl;

  struct __wjf_block_desc_1* Desc;

  __wjf_block_impl_1(void *fp,struct __wjf_block_desc_1 *desc,int flags=0) {

    impl.isa = &_NSConcreteStackBlock;

    impl.Flags = flags;

    impl.FuncPtr = fp;

    Desc = desc;

  }

};


struct __wjf_block_impl_0 {

  struct __block_impl impl;

  struct __wjf_block_desc_0* Desc;

  __Block_byref_i_0 *i; // by ref

  __wjf_block_impl_0(void *fp,struct __wjf_block_desc_0 *desc, __Block_byref_i_0 *_i,int flags=0) : i(_i->__forwarding) {

    impl.isa = &_NSConcreteStackBlock;

    impl.Flags = flags;

    impl.FuncPtr = fp;

    Desc = desc;

  }

};

编译器一共生成了5个结构体,前两个为外部变量的引用而生成,它们都包含isa指针,指向自己的forwarding指针,还有各自的引用变量 i 和block_b。

后三个结构体为block体结构,充分体现了^的对象特性,包含数据也函数。^类型外部变量结构也包含了数据与函数。它们充分体现了^的二重性。正是动态生成的特性,赋予了编程的灵活与强大的功能,同时这种灵活的数据与代码的生成对系统安全可能造成极大的威胁,所以虚拟内存对^类型是不开放的。


后续继续分析,调用机制,先到这里吧。



0 0