• block本质探寻五之atuto类型局部实例对象


    说明:阅读本文章,请参考之前的block文章加以理解;

    一、栈区block分析

    //代码

    //ARC
    void test1()
    {
        {
            Person *per = [[Person alloc] init];
            per.age = 10;
            ^{
                NSLog(@"age:%d", per.age);
            };
        }
        
        NSLog(@"-------1");
    }

     

    //打印

    2019-01-14 17:24:12.118653+0800 MJ_TEST[6638:285938] Person dealloc
    2019-01-14 17:24:12.118934+0800 MJ_TEST[6638:285938] -------1
    Program ended with exit code: 0 

    分析:

    <1>block代码内部引用的Person实例对象先于输出语句销毁,因为per仅限于大括号内,但此时block销毁了没有?往下看;

    <2>上述block代码块并没有被指针持有,接下来看看指针持有的情况;

    //代码

    typedef void(^MyBlock)(void);
    
    //ARC
    void test2()
    {
        MyBlock block;
        
        {
            Person *per = [[Person alloc] init];
            per.age = 10;
            block = ^{
                NSLog(@"age:%d", per.age);
            };
        }
        
        NSLog(@"-------1");
    }

    //打印

    2019-01-14 17:34:58.473267+0800 MJ_TEST[6824:293129] -------1
    2019-01-14 17:34:58.473705+0800 MJ_TEST[6824:293129] Person dealloc
    Program ended with exit code: 0

    分析:Person实例对象后于输出语句销毁,为什么有指针持有,顺序就变了?

    <1>等号左边:是一个auto类型的局部的block指针变量,存放在栈区;等号右边:是一个block代码块(对象),也是一个局部对象,存放在栈区;

    <2>在ARC模式下,如果有指针持有(默认是强指针,修饰符为__strong)一个局部的block对象,系统会自动copy该block对象从栈区到堆区;

    补充:其他三种情况——block作为函数返回值、含usingBlock方法(如数据的枚举方法)、GCD的应用(自己可以验证,此处不再赘述);

    那么,我们再看看MRC的情况

    //打印————test1()和test2()

    2019-01-14 17:56:46.641171+0800 MJ_TEST[7171:306788] -------1
    Program ended with exit code: 0

    分析:为什么per对象没有销毁?——因为需要手动释放;

    //代码

    [per release];

    //打印————test1()和test2()

    2019-01-14 17:59:39.091313+0800 MJ_TEST[7243:309139] Person dealloc
    2019-01-14 17:59:39.091974+0800 MJ_TEST[7243:309139] -------1
    2019-01-14 17:59:39.092013+0800 MJ_TEST[7243:309139] Person dealloc
    2019-01-14 17:59:39.092086+0800 MJ_TEST[7243:309139] -------1
    Program ended with exit code: 0

    分析:

    <1>此时的block对象的作用域在第一个大括号范围内,超出则被释放;

    <2>Person实例对象被捕获到block对象结构体体中,同时其作用域也仅限于第一个大括号内,因此超出同样被释放;

    二、堆区block分析

    1)类型分析——ARC

    //strong类型

    执行上述test2()方法,我们知道系统会自动将block对象从栈区copy到堆区;同时,Person实例对象会被捕捉到block对象的结构体中,如下

    struct __test2_block_impl_0 {
      struct __block_impl impl;
      struct __test2_block_desc_0* Desc;
      Person *per;
      __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, Person *_per, int flags=0) : per(_per) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };

    分析:可以看到per是一个指针变量,而该指针变量默认修饰符为__strong;修改代码

     __strong Person *per = [[Person alloc] init];

    clang命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m

    说明:

    <1>该命令行,只针对ARC模式下,MRC模式下,如果有release语句会报错;

    <2>该命令行,是解决ARC模式下,实例对象为__weak类型,转成C++代码;

    struct __test2_block_impl_0 {
      struct __block_impl impl;
      struct __test2_block_desc_0* Desc;
      Person *__strong per;
      __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, Person *__strong _per, int flags=0) : per(_per) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };

     分析:因此,一般的指针变量,默认修饰符为__strong;

    //weak类型

    //代码

    //ARC
    void test2()
    {
        MyBlock block;
        
        {
            Person *per = [[Person alloc] init];
            per.age = 10;
            __weak Person *weakPer = per;
            block = ^{
                NSLog(@"age:%d", weakPer.age);
            };
    //        [per release];
        }
        
        NSLog(@"-------1");
    }

    //C++代码

    struct __test2_block_impl_0 {
      struct __block_impl impl;
      struct __test2_block_desc_0* Desc;
      Person *__weak weakPer;
      __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, Person *__weak _weakPer, int flags=0) : weakPer(_weakPer) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };

    //打印

    2019-01-15 10:21:36.280746+0800 MJ_TEST[997:45906] Person dealloc
    2019-01-15 10:21:36.281063+0800 MJ_TEST[997:45906] -------1
    Program ended with exit code: 0

    分析:

    <1>此时的per由weakPer弱指针指向,并被捕捉到block对象结构体中;

    <2>结合上述强指针引用Person实例对象的打印结果,weak修饰的指针变量先于输出语句销毁,可以肯定系统是没有对block对象copy到堆区的;

    那针对不同类型指针的引用,系统是如果判断操作的呢?往下看

    2)调用原理

    //C++代码

    typedef void(*MyBlock)(void);
    
    
    
    struct __test2_block_impl_0 {
      struct __block_impl impl;
      struct __test2_block_desc_0* Desc;
      Person *__weak weakPer;
      __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, Person *__weak _weakPer, int flags=0) : weakPer(_weakPer) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __test2_block_func_0(struct __test2_block_impl_0 *__cself) {
      Person *__weak weakPer = __cself->weakPer; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_3e374f_mi_2, ((int (*)(id, SEL))(void *)objc_msgSend)((id)weakPer, sel_registerName("age")));
            }
    
    static void __test2_block_copy_0(struct __test2_block_impl_0*dst, struct __test2_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPer, (void*)src->weakPer, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __test2_block_dispose_0(struct __test2_block_impl_0*src) {_Block_object_dispose((void*)src->weakPer, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __test2_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __test2_block_impl_0*, struct __test2_block_impl_0*);
      void (*dispose)(struct __test2_block_impl_0*);
    } __test2_block_desc_0_DATA = { 0, sizeof(struct __test2_block_impl_0), __test2_block_copy_0, __test2_block_dispose_0};
    
    void test2()
    {
        MyBlock block;
    
        {
            Person *per = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
            ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)per, sel_registerName("setAge:"), 10);
            __attribute__((objc_ownership(weak))) Person *weakPer = per;
            block = ((void (*)())&__test2_block_impl_0((void *)__test2_block_func_0, &__test2_block_desc_0_DATA, weakPer, 570425344));
    
        }
    
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_3e374f_mi_3);
    }
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            test2();
        }
        return 0;
    }

    分析:

    <1>根据前面的文章分析,我们发现block描述结构体(__test2_block_desc_0)中多了两个函数指针的成员变量

    void (*copy)(struct __test2_block_impl_0*, struct __test2_block_impl_0*);
      void (*dispose)(struct __test2_block_impl_0*);

    其中,copy函数指针指向__test2_block_copy_0函数,dispose指向__test2_block_dispose_0;

    <2>__test2_block_copy_0函数主要是通过_Block_object_assign函数来确定对per对象是否强引用,其根据就是per的引用类型——如果是__strong类型,则block对象对per对对象进行强引用(per的生命周期可控);如果是__weak类型,则进行弱引用(per的生命周期不可控);

    <3>当block对像从堆区销毁时,会调用__test2_block_dispose_0函数会自动释放引用的per对象(相当于release)——注:严格意义上,此处的释放指的断开是block对象对per的引用即retainCount减1,至于per对象所占的内存是否被释放(回收)则在所不问(也许还有其他的指针变量引用),只有retainCount变为0零,其内存才会被回收;

    补充:当block访问的外部的auto类型的局部数据为对象时,则会产生上述两个函数指针;如果是非实例对象(如基础数据类型),则不会有上述两个函数指针——原因:实例对象一般是在堆区开辟的内存,需要对其进行内存管理————注:如果是__block修饰前述变量(包括实例对象),也会产生上述两个指针函数,具体后面文章会写到!

    //代码

    void test3()
    {
        int age = 10;
        ^{
            NSLog(@"%d", age);
        };
    }

    //clang

    static struct __test3_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __test3_block_desc_0_DATA = { 0, sizeof(struct __test3_block_impl_0)};

    三、结论 

    【1】栈区block:不论是ARC还是MRC模式,指向该block对象的指针变量,不会对引用的auto类型的局部的实例对象进行强引用;

    【2】堆区block:不论是ARC还是MRC模式,指向该block对象的指针变量,根据引用的auto类型的局部的实例对象的引用类型,通过调用block结构体中的copy函数指针来调用_Block_object_assign函数,来决定对实例对象是否进行强引用——__strong类型强引用,__weak类型弱引用;

    【3】堆区block释放:系统会通过调用block结构体中的dispose函数指针来调用__test2_block_dispose_0函数,自动释放引用的外部auto类型的局部实例对象;

    说明:

    <1>block对象本身,即代码块(位于等号右边),非block指针变量(位于等号左边);

    <2>所谓的强引用,类似于retain操作即保留实例对象(所占内存不随作用域限制而被自动回收),保证手动管理内存释放,达到可控的目的;

    四、拓展——GCD引用分析

    1)

    //代码

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        Person *per = [[Person alloc] init];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*3), dispatch_get_main_queue(), ^{
            NSLog(@"%@", per);
        });
        
        NSLog(@"touchesBegan");
    }

    //打印

    2019-01-15 11:56:19.867668+0800 GCD_Refrence[1558:90109] touchesBegan
    2019-01-15 11:56:22.867823+0800 GCD_Refrence[1558:90109] <Person: 0x600003bb8cf0>
    2019-01-15 11:56:22.867996+0800 GCD_Refrence[1558:90109] Person dealloc

    2)

    //代码

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        Person *per = [[Person alloc] init];
        __weak Person *weakPer = per;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*3), dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakPer);
        });
        
        NSLog(@"touchesBegan");
    }

    //打印

    2019-01-15 11:58:58.381396+0800 GCD_Refrence[1619:93062] touchesBegan
    2019-01-15 11:58:58.381583+0800 GCD_Refrence[1619:93062] Person dealloc
    2019-01-15 11:59:01.381697+0800 GCD_Refrence[1619:93062] (null)

    3)

    //代码

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        Person *per = [[Person alloc] init];
        __weak Person *weakPer = per;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{
                NSLog(@"%@", per);
            });
        });
        
        NSLog(@"touchesBegan");
    }

    //打印

    2019-01-15 12:00:44.996108+0800 GCD_Refrence[1653:94592] touchesBegan
    2019-01-15 12:00:48.088426+0800 GCD_Refrence[1653:94592] <Person: 0x6000010f4a20>
    2019-01-15 12:00:48.088664+0800 GCD_Refrence[1653:94592] Person dealloc

    4)

    //代码

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        Person *per = [[Person alloc] init];
        __weak Person *weakPer = per;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{
                NSLog(@"%@", weakPer);
            });
        });
        
        NSLog(@"touchesBegan");
    }

    //打印

    2019-01-15 12:01:42.122836+0800 GCD_Refrence[1672:95546] touchesBegan
    2019-01-15 12:01:42.123038+0800 GCD_Refrence[1672:95546] Person dealloc
    2019-01-15 12:01:45.123256+0800 GCD_Refrence[1672:95546] (null)

    5)

    //代码

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        Person *per = [[Person alloc] init];
        __weak Person *weakPer = per;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakPer);
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{
                NSLog(@"%@", per);
            });
        });
        
        NSLog(@"touchesBegan");
    }

    //打印

    2019-01-15 12:02:50.591355+0800 GCD_Refrence[1693:96581] touchesBegan
    2019-01-15 12:02:51.685830+0800 GCD_Refrence[1693:96581] <Person: 0x6000033a4470>
    2019-01-15 12:02:53.686541+0800 GCD_Refrence[1693:96581] <Person: 0x6000033a4470>
    2019-01-15 12:02:53.686810+0800 GCD_Refrence[1693:96581] Person dealloc

    6)

    //代码

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        Person *per = [[Person alloc] init];
        __weak Person *weakPer = per;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{
            NSLog(@"%@", per);
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{
                NSLog(@"%@", weakPer);
            });
        });
        
        NSLog(@"touchesBegan");
    }

    //打印

    2019-01-15 12:03:47.349637+0800 GCD_Refrence[1715:97486] touchesBegan
    2019-01-15 12:03:48.447971+0800 GCD_Refrence[1715:97486] <Person: 0x600000163900>
    2019-01-15 12:03:48.448271+0800 GCD_Refrence[1715:97486] Person dealloc
    2019-01-15 12:03:50.448553+0800 GCD_Refrence[1715:97486] (null)

    7)

    //代码

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        Person *per = [[Person alloc] init];
        __weak Person *weakPer = per;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{
            NSLog(@"%@", per);
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{
                NSLog(@"%@", per);
            });
        });
        
        NSLog(@"touchesBegan");
    }

    //打印

    2019-01-15 12:04:37.584067+0800 GCD_Refrence[1735:98332] touchesBegan
    2019-01-15 12:04:38.679922+0800 GCD_Refrence[1735:98332] <Person: 0x600003bd7570>
    2019-01-15 12:04:40.876560+0800 GCD_Refrence[1735:98332] <Person: 0x600003bd7570>
    2019-01-15 12:04:40.876803+0800 GCD_Refrence[1735:98332] Person dealloc

    8)

    //代码

    - (void)test8
    {
        Person *per = [[Person alloc] init];
        __weak Person *weakPer = per;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*3), dispatch_get_main_queue(), ^{
            NSLog(@"1---weakPer--%@", weakPer);
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{
                NSLog(@"2---per--%@", per);
                
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{
                    NSLog(@"3---weakPer--%@", weakPer);
                    
                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{
                        NSLog(@"4---per--%@", per);
                        
                        
                    });
                });
            });
        });
        
        NSLog(@"touchesBegan");
    }
    

     //打印

    2019-05-27 10:27:15.314844+0800 2-2 auto类型局部实例对象GCD[971:43547] touchesBegan
    2019-05-27 10:27:18.315007+0800 2-2 auto类型局部实例对象GCD[971:43547] 1---weakPer--<Person: 0x60000392ab30>
    2019-05-27 10:27:20.315417+0800 2-2 auto类型局部实例对象GCD[971:43547] 2---per--<Person: 0x60000392ab30>
    2019-05-27 10:27:22.315839+0800 2-2 auto类型局部实例对象GCD[971:43547] 3---weakPer--<Person: 0x60000392ab30>
    2019-05-27 10:27:24.316280+0800 2-2 auto类型局部实例对象GCD[971:43547] 4---per--<Person: 0x60000392ab30>
    2019-05-27 10:27:24.316524+0800 2-2 auto类型局部实例对象GCD[971:43547] Person dealloc
    

    分析:

    <1>ARC环境下,使用GCD时,系统自动将block对象从栈区copy到堆区;

    <2>根据以上打印结果,发现如果是单纯的对per进行强引用,则延时3秒后per对象才销毁;如果是弱引用,则立即销毁,再次使用时为空(此时已经被释放);

    <3>如果既对per强引用又有弱引用,在嵌套的GCD使用中,以最后一个强引用为准——即per对象在最后一个强引用执行完后就会释放(之后的弱引用则输出为空);

    结论:同一个auto类型的局部的实例对象,既有强引用,也有弱引用,以强引用为准;

    GitHub

  • 相关阅读:
    Appsacn 定期自动化扫描
    安全扫描工具 AppScan
    安全扫描工具 Netsparker
    Appium环境搭建
    selenium元素定位大全
    浅谈 WebDriver如何应对不同浏览器
    自动化环境搭建
    三次握手四次挥手
    通俗讲解python__new__()方法
    第十三章、元类之控制类的调用过程
  • 原文地址:https://www.cnblogs.com/lybSkill/p/10268273.html
Copyright © 2020-2023  润新知