zoukankan      html  css  js  c++  java
  • block本质探寻四之copy

    说明:

    <1>阅读本文,最好阅读之前的block文章加以理解;

    <2>本文内容:三种block类型的copy情况(MRC)、是否深拷贝、错误copy;

    一、MRC模式下,三种block类型的copy情况

    //代码

    void test1()
    {
        int age = 10;
        
        void(^block1)(void) = ^{
            NSLog(@"-----");
        };
        
        void(^block2)(void) = ^{
            NSLog(@"-----%d", age);
        };
        
        id block3 = [block2 copy];
        
        NSLog(@"%@ %@ %@", [block1 class], [block2 class], [block3 class]);
        NSLog(@"%@ %@ %@", [[block1 copy] class], [[block2 copy] class], [[block3 copy] class]);
    }

    //打印

    2019-01-11 14:14:06.902974+0800 MJ_TEST[2183:154918] __NSGlobalBlock__ __NSStackBlock__ __NSMallocBlock__
    2019-01-11 14:14:06.903260+0800 MJ_TEST[2183:154918] __NSGlobalBlock__ __NSMallocBlock__ __NSMallocBlock__
    Program ended with exit code: 0

    分析:

    <1>只有stack类型block实例对象copy后的类型变为malloc,这个前面文章已经讨论过,没有问题;

    <2>global类型实例对象存储在数据区,copy操作其实什么也没做;malloc在堆区,copy之后肯定还是在堆区,但不会开辟新的内存,只是引用计数加1——此处分析,可以通过clang和地址、引用计数打印来查看,此处不再赘述;

    结论: 

    补充:上述copy的操作是针对block实例对象,那么类对象是存在哪个区呢?往下看

    //代码

    int a = 20;
    
    void test2()
    {
        int b = 10;
        
        void(^block1)(void) = ^{
            NSLog(@"-----");
        };
        
        void(^block2)(void) = ^{
            NSLog(@"-----%d", b);
        };
        
        id block3 = [block2 copy];
        
        id block1Cls = object_getClass(block1);
        id block2Cls = object_getClass(block2);
        id block3Cls = object_getClass(block3);
        
        NSLog(@"a--global--%p", &a);
        NSLog(@"b--auto place--%p", &b);
        NSLog(@"alloc----%p", [[NSObject alloc] init]);
        NSLog(@"Person----%p", [Person class]);
        
        NSLog(@"------block---instance---");
        NSLog(@"block1----%@ %p", [block1 class], block1);
        NSLog(@"block2----%@ %p", [block2 class], block2);
        NSLog(@"block3----%@ %p", [block3 class], block3);
        
        NSLog(@"------block---Class---");
        NSLog(@"block1Cls----%@ %p", block1Cls, block1Cls);
        NSLog(@"block2Cls----%@ %p", block2Cls, block2Cls);
        NSLog(@"block3Cls----%@ %p", block3Cls, block3Cls);
    }

    //打印

    2019-01-11 14:58:29.922125+0800 MJ_TEST[2443:177646] a--global--0x100002520
    2019-01-11 14:58:29.922498+0800 MJ_TEST[2443:177646] b--auto place--0x7ffeefbff59c
    2019-01-11 14:58:29.922525+0800 MJ_TEST[2443:177646] alloc----0x100526420
    2019-01-11 14:58:29.922561+0800 MJ_TEST[2443:177646] Person----0x1000024f8
    2019-01-11 14:58:29.922585+0800 MJ_TEST[2443:177646] ------block---instance---
    2019-01-11 14:58:29.922639+0800 MJ_TEST[2443:177646] block1----__NSGlobalBlock__ 0x1000020c0
    2019-01-11 14:58:29.922666+0800 MJ_TEST[2443:177646] block2----__NSStackBlock__ 0x7ffeefbff560
    2019-01-11 14:58:29.922699+0800 MJ_TEST[2443:177646] block3----__NSMallocBlock__ 0x102812000
    2019-01-11 14:58:29.922717+0800 MJ_TEST[2443:177646] ------block---Class---
    2019-01-11 14:58:29.922736+0800 MJ_TEST[2443:177646] block1Cls----__NSGlobalBlock__ 0x7fffb33c3460
    2019-01-11 14:58:29.922756+0800 MJ_TEST[2443:177646] block2Cls----__NSStackBlock__ 0x7fffb33c3060
    2019-01-11 14:58:29.922777+0800 MJ_TEST[2443:177646] block3Cls----__NSMallocBlock__ 0x7fffb33c3160
    Program ended with exit code: 0

    分析:

    <1>Person类对象:打印出的类对象Person的地址跟全局变量a和global类型block实例对象的地址类似度极高(都以"0x100002"开头),我们知道全局变量a和global类型block实例变量都是存放在数据区(全局区),那么可以肯定类对象也是存放在数据区中;

    <2>block类对象:通过runtime的API我们拿到了三种类型block类对象,发现类对象的地址并不以"0x100002"开头———其中的原因我就懵逼了(内存地址不是很了解),但是可以推断应该也是在数据区,为什么呢?往下看

    //代码

    typedef void(^Block)(void);
    Block block1;
    
    void test3()
    {
        int b = 10;
        
        block1 = ^{
            NSLog(@"-----%d", b);
        };
        
        NSLog(@"%p %p", block1, object_getClass(block1));
        
    }

    //设置全局断点 

    //打印

    2019-01-11 16:34:06.281146+0800 MJ_TEST[3354:234187] 0x7ffeefbff538 0x7fffb33c3060
    2019-01-11 16:34:06.281455+0800 MJ_TEST[3354:234187] ------272632520
    2019-01-11 16:34:06.281477+0800 MJ_TEST[3354:234187] 0x7ffeefbff538 0xf9552b000e0
    2019-01-11 16:34:06.281496+0800 MJ_TEST[3354:234187] 0x7ffeefbff588 0x7fffb33c3060

    分析:

    1)作为auto类型的局部变量,age的作用域仅限于test3()函数内,所以在main函数中再去回调block时,age已经被自动释放(所占内存被回收),所以age的值显示乱码;而同时block1其实也被销毁了,为什么?往下看

    <1>object_getClass(block1)每次返回的值都不同,而其他只都保持不变(已经反复run了多次);

    <2>当我们第二次去回调block1时,如上报出一个很经典的错误——野指针调用,即指针所指向的内存空间已经被回收(即被释放),但是此时并没有对该指针赋值一个新的内存地址或者nil值,该指针变成了一个野指针,指向不明确;

    补充:内存泄露:是指指针一直指向某一片内存空间,但是程序已经不需要再用该内存空间了,但其他的程序又无法调用该内存空间(只能开辟新的内存空间),这样很容易导致内存爆增;所以内存泄露跟野指针调用是完全相反的;

    内存溢出:是指系统分配给程序的内存空间不够用,这样也很容易导致野指针调用的问题;

    <3>对block1进行copy的情形:

    //代码

    void test3()
    {
        int b = 10;
        
        block1 = [^{
            NSLog(@"-----%d", b);
        } copy];
        
        NSLog(@"%p %p", block1, object_getClass(block1));
        
    }
    
    
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
    //        test1();
    //        test2();
            test3();
            block1();
            NSLog(@"%p %p", block1, object_getClass(block1));
            
            int age = 10;
            Block bl = ^{
                NSLog(@"%d", age);
            };
            NSLog(@"%p %p", bl, object_getClass(bl));
            block1();
            block1();
            block1();
            
    //        test4();
    //        block();
            
        }
        return 0;
    }

    //打印

    2019-01-11 16:40:36.144529+0800 MJ_TEST[3397:237736] 0x100526670 0x7fffb33c3160
    2019-01-11 16:40:36.144849+0800 MJ_TEST[3397:237736] -----10
    2019-01-11 16:40:36.144864+0800 MJ_TEST[3397:237736] 0x100526670 0x7fffb33c3160
    2019-01-11 16:40:36.144893+0800 MJ_TEST[3397:237736] 0x7ffeefbff588 0x7fffb33c3060
    2019-01-11 16:40:36.144916+0800 MJ_TEST[3397:237736] -----10
    2019-01-11 16:40:36.144934+0800 MJ_TEST[3397:237736] -----10
    2019-01-11 16:40:36.144950+0800 MJ_TEST[3397:237736] -----10
    Program ended with exit code: 0

    分析:copy之后,block1一直没有被释放(堆区需要手动管理),即block1一直指向了合法的内存空间,因此不会出现野指针调用的bug;

    综上:block1是一个指针变量,其指向等号右边的代码块本质是一个oc对象,存放在栈区中,当回调该代码块时,其已经被自动释放,但是block1因为没有重新赋值而变成了野指针,所以block1指向的代码块是已经被销毁了的;

    2)block1销毁后,新创建的bl打印出的类对象的地址跟block1销毁前打印出的地址都是0x7fffb33c3060,因为类对象在内存中只有一份,据此,block1的类对象并没有随着block1的销毁而销毁,所以block的类对象不可能存在于栈区,同一个block类对象供所有创建的block实例对象的isa指针访问并且类对象是系统自动创建并管理的,因此也不可能存在于堆区,也不会存在于代码区

    ————结论:block类对象跟其他OC实例对象的类对象一样,都只存在于数据区!!!

    二、block拷贝是否深拷贝

    //代码

    void test4()
    {
        int age = 10;
        int *agePtr = &age;
        NSLog(@"age---1:
    %d %p %d %p %p", age, &age, *agePtr, agePtr, &agePtr);
        
        block1 = [^{
            NSLog(@"age----2:
    %d %p %d %p %p", age, &age, *agePtr, agePtr, &agePtr);
        } copy];
        
    }

    //打印

    2019-01-14 09:55:33.399468+0800 MJ_TEST[907:35484] age---1:
    10 0x7ffeefbff59c 10 0x7ffeefbff59c 0x7ffeefbff590
    2019-01-14 09:55:33.399735+0800 MJ_TEST[907:35484] age----2:
    10 0x100400238 1 0x7ffeefbff59c 0x100400230
    Program ended with exit code: 0

    分析:

    <1>copy后,age、agePtr自身的地址值都发生了变化,说明两个变量都从栈区拷贝到了堆区;

    <2>指针变量的值不再是10而是1(乱码),因为指针变量依然指向age拷贝前的内存区域,而该内存区随时可能被释放;

    我们再看看对nsstring字符串的深拷贝(mutableCopy)和浅拷贝(copy)操作

    //代码

    void test5()
    {
        NSString *strSource = @"abc";
        
        NSLog(@"source:
    %@ %p %p", strSource, strSource, &strSource);
        
        NSString *str1 = [strSource copy];
        
        NSLog(@"str1:
    %@ %p %p", str1, str1, &str1);
        
        NSString *str2 = [strSource mutableCopy];
        
        NSLog(@"str2:
    %@ %p %p", str2, str2, &str2);
        
    }

    //打印

    2019-01-14 10:14:26.299400+0800 MJ_TEST[1066:45457] source:
    abc 0x1000023a0 0x7ffeefbff598
    2019-01-14 10:14:26.299783+0800 MJ_TEST[1066:45457] str1:
    abc 0x1000023a0 0x7ffeefbff590
    2019-01-14 10:14:26.299897+0800 MJ_TEST[1066:45457] str2:
    abc 0x100507170 0x7ffeefbff588
    Program ended with exit code: 0

    分析:

    <1>很明显,浅拷贝只拷贝了指针变量str1(从代码区(常量区)到堆区),该指针依然指向代码区常量abc的内存区;

    <2>深拷贝不仅指针变量被拷贝到堆区,而且常量abc也被拷贝到了堆区;

    说明:深拷贝和浅拷贝区别,见参考链接:https://www.jianshu.com/p/63239d4d65e0;

    //代码

    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    struct __main_block_desc_0 {
        size_t reserved;
        size_t Block_size;
    };
    
    struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
        int age;
    };
    
    int age = 10;
    typedef void(^MyBlock)(void);
    void(^deptCopyBlock)(void);
    
    void test2()
    {
        MyBlock stackBlock;
        NSString *name = @"yy";
        
        //栈区block
        stackBlock = ^{
            NSLog(@"-----%@", name);
        };
        //堆区block
        MyBlock mallocBlock = [stackBlock copy];
        
        NSLog(@"stackBlock--%p---%p
    %@", stackBlock, &stackBlock, [stackBlock class]);
        NSLog(@"mallocBlock--%p---%p
    %@", mallocBlock, &mallocBlock, [mallocBlock class]);
        
        struct __main_block_impl_0 *stackStruct = (__bridge struct __main_block_impl_0 *)stackBlock;
        struct __main_block_impl_0 *mallocStruct = (__bridge struct __main_block_impl_0 *)mallocBlock;
        
        //打断点
        NSLog(@"----");
    }
    

    //打印

     

    综上所述:

    1)从上述test5()方法看出:block的拷贝均拷贝了指针和该指针指向的值到堆区,但是新的指针却依然指向拷贝前的内存区域;

    2)从上述test2()方法,根据打印isa和FuncPtr指针本身地址,及指向地址:在栈区的指针及其值均被拷贝到堆区(如isa),为深拷贝;而其他指针指不在栈区的(如FuncPtr),仅拷贝了指针本事,指针值没有拷贝,为浅拷贝;

    所以,block从栈区拷贝到堆区,既有深拷贝,也有浅拷贝;

    说明:此处拷贝:针对的是block内部成员变量,而非block指针;所以,&stackBlock和&mallocBlock都在栈区(自定义auto局部变量);

    三、错误copy

    //代码

    void(^block)(void);
    
    void test6()
    {
        int age = 10;
        NSLog(@"age----%p", &age);
        
        block = ^{
            NSLog(@"age----%p", &age);
            NSLog(@"----%d", age);
        };
        
        
        
        NSLog(@"block--1---%p", block);
        NSLog(@"block class--1---%p", [block class]);
        id coBlock = [block copy];
        NSLog(@"%@", [coBlock class]);
        NSLog(@"block--2---%p", coBlock);
        NSLog(@"block class--2---%p", [coBlock class]);
    }

    //打印

    2019-01-14 10:31:32.767665+0800 MJ_TEST[1159:53399] age----0x7ffeefbff59c
    2019-01-14 10:31:32.767975+0800 MJ_TEST[1159:53399] block--1---0x7ffeefbff578
    2019-01-14 10:31:32.768027+0800 MJ_TEST[1159:53399] block class--1---0x7fff8e0fe060
    2019-01-14 10:31:32.768075+0800 MJ_TEST[1159:53399] __NSMallocBlock__
    2019-01-14 10:31:32.768094+0800 MJ_TEST[1159:53399] block--2---0x100729380
    2019-01-14 10:31:32.768111+0800 MJ_TEST[1159:53399] block class--2---0x7fff8e0fe160
    2019-01-14 10:31:32.768127+0800 MJ_TEST[1159:53399] age----0x7ffeefbff598
    2019-01-14 10:31:32.768141+0800 MJ_TEST[1159:53399] -----272632456
    Program ended with exit code: 0

    分析:

    <1>block:copy前后,block的地址发生了变化,因为block从栈区被拷贝到堆区了,这一点没问题;那么block的类对象地址也发生了变化,因为copy前block的类型为stack类型,之后是malloc类型(系统会自动创建一个类对象),前者存放在栈区,后者存放在堆区,所以也没问题;

    <2>age:并没有被copy 到堆区,block回调时,已经被释放,其值为乱码,这点没问题;但是age的地址值这么发生变化了?我们再往下看

    //代码

    void test7()
    {
        int age = 10;
        int *agePtr = &age;
        NSLog(@"1----
    %d %p %d %p %p", age, &age, *agePtr, agePtr, &agePtr);
        
        block = ^{
            NSLog(@"1----
    %d %p %d %p %p", age, &age, *agePtr, agePtr, &agePtr);
        };
        
        id coBlock = [block copy];
    }

    //打印

    2019-01-14 10:54:30.119695+0800 MJ_TEST[1281:64385] 1----
    10 0x7ffeefbff59c 10 0x7ffeefbff59c 0x7ffeefbff590
    2019-01-14 10:54:30.119992+0800 MJ_TEST[1281:64385] 1----
    10 0x7ffeefbff588 32766 0x7ffeefbff59c 0x7ffeefbff580
    Program ended with exit code: 0

    分析:

    <1>不论是age还是agePtr,block回调时,本身的地址都会发生变化,因为所占内存都被释放,内存地址不回固定,系统会重新编排(个人YY,具体不清楚);

    <2>但是,尽管age的值变成乱码,而指针变量agePtr的值却没变依然是原age的地址值——为什么指针变量的内存值不是乱码呢?也许是因为代码区(常量区)跟栈区、堆区的存储规则的区别,指针变量本身已经被释放,其值变与不变好像没有多大的意义——但是,从代码规范角度,被释放后,应当将指针变量置为nil,防止野指针调用!

    结论:所谓错误的copy只是copy了block指针变量(等号左边),并非block代码块本身(等号右边)——因此,被引用的外部auto类型的局部变量不会被copy到堆区;

     

    GitHub

  • 相关阅读:
    Activity生命周期 onCreate onResume onStop onPause (转)
    职场链接
    memset中的sizeof
    c文件操作 (转)
    串口编程 tcflush()函数 (转)
    tcflush 功能(转)
    串口开发(转)
    ios github网址
    做移动端视频通话软件,大致看了下现有的开源软件(转)
    PJSIP框架
  • 原文地址:https://www.cnblogs.com/lybSkill/p/10256618.html
Copyright © 2011-2022 走看看