iOS Block源码分析系列(三)————隐藏的三种Block本体以及为什么要使用copy修饰符

到这个阶段,我们用C的结构编译的代码以及源码能看到Block结构体内部的isa指针是指向_NSContreteStackBlock的,其实这只是其中的一种,分别还有_NSContreteGlobalBlock 和 _NSContreteMallocBlock,可以根据命名的后缀看来StackBlock是设置在栈上的,GlobalBlock就类似全局变量,设置在程序的数据区域(.data区域),那么最重的是我们写OC代码的时候根本不关注的一种类型NSContreteMallocBlock,没错,他就是和对象一样分类内存块中(堆中)。


_NSContreteGlobalBlock

你可以把它理解为全局变量,反正存储在.data区域的,最直接得写法是这样的

void (^block)(void) = ^{};
int main(){}
转换过后的源码指针impl.isa = &_NSContreteGlobalBlock类型的

由于在使用全局变量的地方不能使用局部变量,这么说来就根本不存在对局部变量的捕获。那么这个Block的结构体实例的内容压根不会再进行追加成员变量,所以不会依赖于执行状态,所以整个程序运行只有一个实例。因此将Block使用的结构体实例设置在与全局变量相同的数据区域即可。(把它理解为单纯的全局变量就好了,而且不会有任何值的捕获)

存在的两种案例

@ 全局变量的地方这种Block语法时,如上面所示

@ Block语法中表达式不截获任何局部变量时,这个稍后Demo介绍,也很简单

除了上面这种之外,那么就是我们前面两集提到的_NSContreteStackBlock,设置在栈上。



那么就产生了上一节遗留下来的问题

1.Block既然是栈上的,那么出作用域之后是如何存在使用的?

2.__block里面_forwarding存在的理由?

__NSContreteMallocBlock


配置在全局的GlobalBlock可以出了作用域还是能继续访问,但是在栈上的StackBlock就废弃了,因此为了出了作用域能继续使用,Blocks提供了把Block和__block这两个东西从栈上复制到堆上的方法来解决这个问题。而_forwarding其实既可以指向自己,也可以指向复制后的自己,也就是说有了这个指针的存在,无论__block变量配置在堆上还是栈上都能够正确的访问__block变量

根据资料介绍一种作为返回值返回的情

typedef void(^block)(void);

block func (int a)
{
    return ^{};
}

转换后

block func (int a)
{
    block tmp = ((void (*)())&__func_block_impl_0((void *)__func_block_func_0, &__func_block_desc_0_DATA));
    tmp = objc_retainBlock(tmp);
    return objc_autoreleaseReturnValue(tmp);
}


这里先是通过Block语法生成Block,即生成配置在栈上的Block结构体实例,然后赋值给Block类型的变量tmp中,然后执行objc_retainBlock(tmp)这句其实就是_Block_copy(tmp),将栈上的的Block复制到堆上,赋值后将堆上的指针赋值给tmp,然后堆上的所有都是对象,需要注册陶autoreleasepool中进行管理,然后返回其对象

其实这种内部调用方式,MallocBlock就是对象,而且这种写法是否很似曾相识,就是类方法实例化

- (NSArray *) myTestArray {
    NSArray *array = [[NSArray alloc] initWithObjects: @"a", @"b", @"c", nil];
    return [array autorelease];
}

简单来说就是第一步copyBlock到堆上,然后和OC对象一样,返回的对象需要进行autorelease防治内存泄露

ARC下面很多都已经自动帮我们Copy成了MallocBlock了,请看一下几种情况

由于Block是默认建立在栈上,所以如果离开方法作用域, Block就会被丢弃,在非ARC情况下,我们要返回一个Block ,需要 [Block copy];

    

ARC,以下几种情况, Block会自动被从栈复制到堆:

    1.被执行copy方法

    2.作为方法返回值

    3.Block赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时

    4.在方法名中含有usingBlockCocoa框架方法或者GDCAPI中传递的时候.

    int a = 1;
    
    // 这里的^{}初始化的block赋值给block变量,在OC中没有具体写明的情况下应该就是strong类型的,这就是上面第三点的例子
    // 打印出来 first <__NSMallocBlock__: 0x60800004d290>
    void (^block)(void) = ^{
        NSLog(@"%d",a);
    };
    NSLog(@"first %@",block);
    
    // 没有截获变量的时候就是globalBlock
    // second<__NSGlobalBlock__: 0x102f80100>
    NSLog(@"second%@",^{NSLog(@"呵呵");});
    
    // 截获了变量就是stackBlock
    // third<__NSStackBlock__: 0x7fff5cc7faa0>
    NSLog(@"third%@",^{NSLog(@"%d",a);});
    
    
    __block int val = 10;
    __strong blk strongPointerBlock = ^{NSLog(@"val1 = %d", ++val);};
    // strongPointerBlock: <__NSMallocBlock__: 0x600000044e90>
    NSLog(@"strongPointerBlock: %@", strongPointerBlock); //1
    
    __weak blk weakPointerBlock = ^{NSLog(@"val2 = %d", ++val);};
    // weakPointerBlock: <__NSStackBlock__: 0x7fff5282ea70>
    NSLog(@"weakPointerBlock: %@", weakPointerBlock); //2
    
    
    // mallocBlock3: <__NSMallocBlock__: 0x608000046930>
    NSLog(@"mallocBlock3: %@", [weakPointerBlock copy]); //3
    
    
    // 截获了test <__NSStackBlock__: 0x7fff5282ea48>
    NSLog(@"test4 %@", ^{NSLog(@"val4 = %d", ++val);}); //4
    
    // test5 <__NSMallocBlock__: 0x60800005c0b0>
    NSLog(@"test5 %@", [^{NSLog(@"val4 = %d", ++val);} copy]); //5
    
    
    // stackBlock经过传参 打印
    NSLog(@"传参后 %@",[self getBlock]);
}


- (blk)getBlock
{
    int val = 11;
    // 上面已经介绍了,这种直接打印传参前的block,应该是__NSStackBlock__
    NSLog(@"传参前:%@",^{NSLog(@"%d",val);});
    
    // 那么现在我们直接传出去
    return ^{NSLog(@"%d",val);};
}

这里把几种情况都打印了一下看看到底是哪个类型

1.第一个和第二个打印的区别在于,第一个生成的Block默认赋值给了block变量,第二个直接打印,由于OC里面没有修饰符默认就是strong,所以这么看来遵循第三条规则之后,第二条条打印就是_NSGlobalBlock_(没有截获变量),第一条打印就是_NSMallocBlock_(自动复制到堆上了)

2.后面用strong修饰符和weak修饰符分别打印的是malloc的和stack的,但是无论哪种,只要copy就是变成malloc类型了

3.最后一种就是上面介绍的,stackBlock经过传参,自动变成了mallocBlock


来看一张表,看看三种类型copy之后有什么区别


这个图告诉我们,无论什么情况,copy方法不会引起任何的问题,在不确定的时候调用copy就可以了



@property (nonatomic,copy)blk blk;

当我们这么声明属性的时候,其setter方法就是用了copy方法

- (void)setBlk:(blk)blk
{
    if (_blk != blk) {
        [_blk release];//MRC
        _blk = [blk copy];
    }
}
这也就可以理解Block配上Copy属性是再好不过了,根本不用担心会出什么问题,表也说明了这点

总结:

1.超出作用于存在的理由就是生成了MallocBlock对象,即使出栈了还是能继续调用

2.forwarding的理由就是无论在堆上还是栈上,我们都能访问Block,而且能保证访问同一个

3.GlobalBlock 、MallocBlock和StackBlock的区别以及如何Block如何会被copy到堆上

4.特别上作为参数传递时,类似类方法的autoreleasepool注册进去,避免内存泄露

5.在正常写代码的时候不需要管理这个,默认百分之99的情况基本都是MallocBlock

6.无论什么情况下,copy一下或者强指针引用一下是不会有错的,能保证必然是MallocBlock

7.各种迹象表明,他就是一个对象,可以通过copy改变类型的特殊对象!!!!!!




有些不对的理解希望大神们指正我,小弟需要不断进步,太晚了,先睡了~~~~~~还有一集就差不多讲完了

iOS Block源码分析系列(二)————局部变量的截获以及__block的作用和理解

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值