美文网首页
iOS block详细知识点

iOS block详细知识点

作者: 恋空K | 来源:发表于2019-07-21 17:36 被阅读0次

    Block与外界变量

    1、截获自动变量(局部变量)值

    (1)默认情况

    对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只针对block内部使用的自动变量, 不使用则不截获, 因为截获的自动变量会存储于block的结构体内部, 会导致block体积变大。特别要注意的是默认情况下block只能访问不能修改局部变量的值。

    (2) __block 修饰的外部变量

    对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的。block可以修改__block 修饰的外部变量的值。

    int age = 10;

    myBlock block = ^{

        NSLog(@"age = %d", age);

    };

    age = 18;

    block();

    输出结果:

    age = 10

    __block int age = 10;

    myBlock block = ^{

        NSLog(@"age = %d", age);

    };

    age = 18;

    block();

    输出为:

    age = 18

    block有三种类型:

    全局块(_NSConcreteGlobalBlock)

    栈块(_NSConcreteStackBlock)

    堆块(_NSConcreteMallocBlock)

    局块存在于全局内存中, 相当于单例.

    栈块存在于栈内存中, 超出其作用域则马上被销毁

    堆块存在于堆内存中, 是一个带引用计数的对象, 需要自行管理其内存

    简而言之,存储在栈中的Block就是栈块、存储在堆中的就是堆块、既不在栈中也不在堆中的块就是全局块。

    遇到一个Block,我们怎么这个Block的存储位置呢?

    (1)Block不访问外界变量(包括栈中和堆中的变量)

    Block 既不在栈又不在堆中,在代码段中,ARC和MRC下都是如此。此时为全局块。

    (2)Block访问外界变量

    MRC 环境下:访问外界变量的 Block 默认存储中。

    ARC 环境下:访问外界变量的 Block 默认存储在中(实际是放在栈区,然后ARC情况下自动又拷贝到堆区),自动释放。

    局部变量可以称为自动变量

    截获自动变量值

    Block表达式可截获所使用的自动变量的值。截获:保存自动变量的瞬间值。因为是“瞬间值”,所以声明Block之后,即便在Block外修改自动变量的值,也不会对Block内截获的自动变量值产生影响。例如:

    __block说明符号

    自动变量截获的值为Block声明时刻的瞬间值,保存后就不能改写该值,如需对自动变量进行重新赋值,需要在变量声明前附加__block说明符,这时该变量称为__block变量。

    自动变量值为一个对象情况

    当自动变量为一个类的对象,且没有使用__block修饰时,虽然不可以在Block内对该变量进行重新赋值,但可以修改该对象的属性。如果该对象是个Mutable的对象,例如NSMutableArray,则还可以在Block内对NSMutableArray进行元素的增删:

    NSMutableArray*array=[[NSMutableArrayalloc]initWithObjects:@"1",@"2",nil];

    NSLog(@"Array Count:%ld",array.count);//打印Array Count:2

    void(^blk)(void)=^{

    [arrayremoveObjectAtIndex:0];//Ok

    //array = [NSNSMutableArray new];//没有__block修饰,编译失败!

    };

    blk();

    NSLog(@"Array Count:%ld",array.count);//打印Array Count:1

    Block也是Objective-C中的对象

    使用__block发生了什么

    Block捕获的自动变量添加__block说明符,就可在Block内读和写该变量,也可以在原来的栈上读写该变量。自动变量的截获保证了栈上的自动变量被销毁后,Block内仍可使用该变量。__block保证了栈上和Block内(通常在堆上)可以访问和修改“同一个变量”,__block是如何实现这一功能的?

    __block发挥作用的原理:将栈上用__block修饰的自动变量封装成一个结构体,让其在堆上创建,以方便从栈上或堆上访问和修改同一份数据。

    1.2 带有自动变量

    关于“带有自动变量(局部变量)”的含义,这是因为Block拥有捕获外部变量的功能。在Block中访问一个外部的局部变量,Block会持用它的临时状态,自动捕获变量值,外部局部变量的变化不会影响它的的状态。

    捕获外部变量,看一个经典block面试题:

    int val = 10; void (^blk)(void) = ^{ printf("val=%d\n",val);

    }; 

    val = 2; 

    blk();

    上面这段代码,输出值是:val = 10,而不是2。

    block 在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝,然后在 block 块内使用该只读拷贝;换句话说block截获自动变量的瞬时值;或者block捕获的是自动变量的副本。

    由于block捕获了自动变量的瞬时值,所以在执行block语法后,即使改写block中使用的自动变量的值也不会影响block执行时自动变量的值。

    4.3 Block引起的循环引用

    一般来说我们总会在设置Block之后,在合适的时间回调Block,而不希望回调Block的时候Block已经被释放了,所以我们需要对Block进行copy,copy到堆中,以便后用。

    Block可能会导致循环引用问题,因为block在拷贝到堆上的时候,会retain其引用的外部变量,那么如果block中如果引用了他的宿主对象,那很有可能引起循环引用

    所有的Block里面的self必须要weak一下?

    很显然答案不都是,有些情况下是可以直接使用self的,比如调用系统的方法:

    [UIView animateWithDuration:0.5 animations:^{ NSLog(@"%@", self);

        }];

    因为这个block存在于静态方法中,虽然block对self强引用着,但是self却不持有这个静态方法,所以完全可以在block内部使用self。

    并不是 block 就一定会造成循环引用,是不是循环引用要看是不是相互持有强引用。block 里用到了 self,那 block 会保持一个 self 的引用,但是 self 并没有直接或者间接持有 block,所以不会造成循环引用。

    注意观察,这个作为方法参数的Block体并没有被任何方持有。因此,我们放心在Masonry中使用self.xxx 不会循环引用的。而且这个block里面用weakSelf还有可能会出问题,因为mas_qeual如果得到一个nil参数的话应该会导致程序崩溃。

    因为UIView未强持有block,所以这个block只是个栈block,而且构不成循环引用的条件。栈block有个特性就是它执行完毕之后就出栈,出栈了就会被释放掉。看mas_makexxx的方法实现会发现这个block很快就被调用了,完事儿就出栈销毁,构不成循环引用,所以可以直接放心的使self。另外,这个与网络请求里面使用self道理是一样的。

    注意:在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block。

    设置在栈上的block,如果其作用域结束,该block就被销毁。同样的,由于__block变量也配置在栈上,如果其作用域结束,则该__block变量也会被销毁。

    位于堆内存:MallocBlock

    堆中的block无法直接创建,其需要由_NSConcreteStackBlock类型的block拷贝而来(也就是说block需要执行copy之后才能存放到堆中)。由于block的拷贝最终都会调用_Block_copy_internal函数。

    ARC下会默认把栈block被会直接拷贝生成到堆上

    在全局block调用copy什么也不做

    在栈上调用copy那么复制到堆上

    在堆上调用block 引用计数增加

    block对于外部变量默认是只读属性

    block被Objective-C看成是对象处理

    循环引用

    开头说过,block在iOS开发中被视作是对象,因此其生命周期会一直等到持有者的生命周期结束了才会结束。另一方面,由于block捕获变量的机制,使得持有block的对象也可能被block持有,从而形成循环引用,导致两者都不能被释放:

    1、截获自动变量(局部变量)值->自动变量就是局部变量

    (1)默认情况

    对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只针对block内部使用的自动变量, 不使用则不截获, 因为截获的自动变量会存储于block的结构体内部, 会导致block体积变大。特别要注意的是默认情况下block只能访问不能修改局部变量的值。

    因为block变量默认是声明为栈变量的,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆,所以说为了block属性声明和实际的操作一致,最好声明为copy。

    相关文章

      网友评论

          本文标题:iOS block详细知识点

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