美文网首页
block记录

block记录

作者: 越来越胖了 | 来源:发表于2019-10-17 18: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真的是很复杂的东西,可能分配在栈区、堆区、常量区,虽然日常中我们基本上可以无视这些区别,看似没有什么用,然而对自己来说,对做技术来说,多较些真,这样才能走的更远吧。

    • "==" ,比较两个指针的值
    • isEqualToString,比较两个字符串是否相同
    • isEqual,判断是一个类方法,判断两个对象在类型和值上是否一样
    1. block的释放问题(待补充)

    相关文章

      网友评论

          本文标题:block记录

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