block记录

2019-10-17  本文已影响0人  越来越胖了

1. block的分类

MRC下,block有三种 NSGlobalBlock(全局的), NSMallocBlock(堆区) ,NSStackBlock(栈区) ;在ARC下,系统实现对内存的管理,同时把NSStackBlock 默认优化成了 NSMallocBlock

如下,就是一个NSGlobalBlock

 void(^block)(void) = ^{
        NSLog(@"block");
        
    };
    
    block();
NSLog(@"%@",block); // <__NSGlobalBlock__: 0x1085ad038> 

如下block引入了外部变量,就是一个堆block

 int a = 10;
    void(^block2)(void) = ^{
        NSLog(@"%d",a);
        NSLog(@"block2");
    };
    block2(); //引入了外部变量,堆block
    NSLog(@"%@",block2);// <__NSMallocBlock__: 0x6000022b50b0>  // Malloc 分配内存 的意思

上面的block2,如果在MRC下就是 NSStackBlock,ARC下系统把block从栈区移到了堆区;
ARC下,假如block2是NSStackBlock类型,如果我们把block2当作方法参数传递到了另外一个方法,那么一旦block2不在原来方法的调用栈,而新方法调用block2的时机又不得而知,可能在新方法调用的时候block2的时候堆区的obj已经被释放。即:ARC下编译器在NSStackBlock类型的block传递过程中进行了自动优化;

2. 采用__block修饰的原因

这里主要通过cpp的代码,了解一个变量,在使用__block 和不适用的情况下,block捕获这个变量时,有什么区别来了解内部的原因

1. 首先,创建一个block.c文件,执行一个简单的block代码

#include "stdio.h"
     int main(){
         void (^block)(void) = ^{
             printf("V15");

         };
         block();
         return 0;
     }

  1. 通过cpp编译成C++代码
clang -rewrite-objc block.c
Pasted Graphic 6.png

苹果的ObjectiveC官方文档中在“Working with Blocks”明确说明:

“ Blocks are Objective-C objects, which means they can be added to collections like NSArray or NSDictionary. ”

可见, Block是Objective C语言中的对象

  1. 添加一个不加__block的变量
int a = 10;
void (^block)(void) = ^{
             printf("V15");
             printf(“%d”,a);

         };
Pasted Graphic 8.png

block内部创建了一个变量,然后获取了a的值存储到了变量上,它只是使用了a的这个值,所以内部对这个a进行修改的情况下,和外部的变量a是完全没有关系的;

  1. 如果使__block修饰,得到的 cpp如下
__block int a = 10;
    void (^block)(void) = ^{
        printf("V15");
        printf("%d",a);
    };
Pasted Graphic 9.png
Pasted Graphic 10.png

传的是a的指针地址了,block中得到a的指针地址(指针地址的传递);__Block_byref_a_0 的意思就是由栈copy到堆中;block内操作的是堆的指针地址了

block不一定用copy,用strong修饰也是可以的 MRC下必须用copy ARC下,是都可以的
其他知识:
assign 与 weak, 它们都是弱应用声明类型, 最大的区别在哪呢?
weak 声明的变量对象释放后自动清空, 赋值为nil
assign声明的变量对象释放后不会自动赋值为nil, 会造成野指针错误!

3. block的具体使用

  1. 做自由变量:
void (^testBlock) (NSString *);
    testBlock = ^(NSString *name) {
        NSLog(@"-----%@",name);
    };
    testBlock(@"我的名字");
    
    
    int (^numBlock)(int) = ^(int num){
        return 10 * num;
    };
    int num_10 = numBlock(8);
    NSLog(@"%d",num_10);
  1. 传值 和 传址 __block
 __block int a = 10;// 传值 和 传址 __block
    
     NSLog(@"定义前:%p", &a);//栈区
    
    void (^block_2)(void) = ^{
        NSLog(@"%d",a);
        NSLog(@"block内部:%p", &a);    //堆区
    };
    
    NSLog(@"定义后:%p", &a);
    🔥block内有对a的调用,则 a会被放在堆区;没有对a的调用,则不会移动到堆区;
    🔥和block代码块是否调用无关,因为编译期就会执行__Block_byref_a_0 *)&a,
//    block_2();

打印:
定义前:0x7ffee136e728
定义后:0x6000000595d8

可变字符串传入block:

NSMutableString * a = [NSMutableString stringWithFormat:@"Tom"];
//NSMutableString * __block a = [NSMutableString stringWithFormat:@"Tom"];
   🔥其实这里加__block,指针方面没有什么变化,但是可以解决系统因为没有识别到 __block而出现的报错,一般不会这样写,这里只是为了去更好的理解.
    NSLog(@"\n 1定义前:------a指向的堆中地址:%p;a在栈中的指针地址:%p", a, &a);
    
    void (^stringBlock)(void) = ^{
        a.string = @"Jerry";
        NSLog(@"\n 2定义后:------a指向的堆中地址:%p;a的指针地址:%p", a, &a);//打印后对比可以做到,其实a的指针已经在堆中了 0_0,这也是为什么可以j修改a的值的原因.
//        a = [NSMutableString stringWithFormat:@"Cooci"];//会提示不可赋值,要加 __block
        NSLog(@"a== %@",a);
        NSLog(@"\n 3定义后:------a指向的堆中地址:%p;a的指针地址:%p", a, &a);
    };
    
    stringBlock();
   

Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。
block在执行的时候有可能自动变量已经被销毁了,那么此时如果再去访问被销毁的地址肯定会发生坏内存访问,
因此对于自动变量一定是值传递而不可能是指针传递了。而静态变量不会被销毁,所以完全可以传递地址。
而因为传递的是值得地址,所以在block调用之前修改地址中保存的值,block中的地址是不会变得。所以值会随之改变

转cpp文件,看的更透彻:

__block int a = 10;
    void (^MyBlock)(void) = ^{
        NSLog(@"%d",a);
    };
    MyBlock();

对应的cpp

cpp文件
 
 
 ***加了 __block的***🔥
 __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
 
 void (*MyBlock)(void) = ((void (*)())&__BlockViewController__viewDidLoad_block_impl_0((void *)__BlockViewController__viewDidLoad_block_func_0, &__BlockViewController__viewDidLoad_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
 ((void (*)(__block_impl *))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock);
 
 ***没添加__block的***🔥
 int a = 10;
 void (*MyBlock)(void) = ((void (*)())&__BlockViewController__viewDidLoad_block_impl_0((void *)__BlockViewController__viewDidLoad_block_func_0, &__BlockViewController__viewDidLoad_block_desc_0_DATA, a));
 ((void (*)(__block_impl *))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock);
 
 void (*MyBlock)(void)  --->没有参数没有返回值的函数指针 ,说明右侧会给到一个指针给 *MyBlock;🔥
 
 其中__BlockViewController__viewDidLoad_block_impl_0是一个结构体🔥
 
 struct __BlockViewController__viewDidLoad_block_impl_0 {
 struct __block_impl impl;
 struct __BlockViewController__viewDidLoad_block_desc_0* Desc;
 int a;
 
  *MyBlock应该就是获得这段代码的地址🔥
 __BlockViewController__viewDidLoad_block_impl_0(void *fp, struct __BlockViewController__viewDidLoad_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
 impl.isa = &_NSConcreteStackBlock;
 impl.Flags = flags;
 impl.FuncPtr = fp;
 Desc = desc;
 }
 };
 


  1. 字符串的存储区(看到了就记录下,和block没关系)
    NSString *c = @"1";  🔥常量字符串,存储在常量区 __NSCFConstantString  
    NSLog(@"%@ : %p",c.class,c);  //__NSCFConstantString : 0x106dec338
    NSString *e = [NSString stringWithFormat:@"1"];
    NSLog(@"%@ : %p",e.class,e);//NSTaggedPointerString : 0xdac8f48553b5a989
🔥NSTaggedPointerString  没有ISA指针的,它根本不是一个对象;NSTaggedPointerString根本不是对象,是分配在栈区的;
    🔥具体可以查看  http://www.cocoachina.com/articles/13449
    

为什么字面量常量 c 苹果不使用NSTaggedPointerString呢 ? 而 e 却使用?
原因是常量字符串需要在跨系统上保持二进制兼容,而 tagged pointers在技术上并不能保证这个。因此对于这种短的字符串字面量还是使用__NSCFConstantString类型。 Tagged Pointer指针的值不再是地址了,而是真正的值;所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。

为什么会引入 Tagged Pointer ?
https://www.infoq.cn/article/deep-understanding-of-tagged-pointer/
NSString真的是很复杂的东西,可能分配在栈区、堆区、常量区,虽然日常中我们基本上可以无视这些区别,看似没有什么用,然而对自己来说,对做技术来说,多较些真,这样才能走的更远吧。

  1. block的释放问题(待补充)
上一篇下一篇

猜你喜欢

热点阅读