美文网首页
02 - Block(内存管理篇)

02 - Block(内存管理篇)

作者: Orely | 来源:发表于2021-02-22 16:47 被阅读0次

    继续上面的基础篇内容。

    1 - block的强引用与弱引用

    我们先看一下这样的代码

    #import <Foundation/Foundation.h>
    
    @interface TestObject : NSObject
    
    @property (nonatomic , assign) int age;
    
    @end
    
    @implementation TestObject
    
    - (void)dealloc {
        NSLog(@"%s",__func__);
    }
    
    @end
    
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            {
                void (^gloadBlockC)(void);
                {
                    TestObject *testObj = [[TestObject alloc] init];
                    testObj.age = 3;
                    
                    TestObject *weakTest = testObj;
                    gloadBlockC = ^void(){
                        NSLog(@"%d",weakTest.age);
                    };
                    
                    testObj.age = 5;
                }
                gloadBlockC();
            }
            
            NSLog(@"执行了");
        }
        return 0;
    }
    

    在ARC环境下,输出结果是:

    5
    -[TestObject dealloc]
    执行了

    在MRC环境下,gloadBlockC();还未执行时

    -[TestObject dealloc]

    执行gloadBlockC()的时候,发生崩溃Thread 1: EXC_BAD_ACCESS (code=1, address=0x18)
    结合上一篇的记录,在MRC环境下,捕获auto变量的block是stackBlock,存储在栈中。而在ARC环境下,当stackBlock赋值给__strong指针的时候,会自动copy,变成了MallocBlock。所以,栈上的block,不会对捕获的auto变量强引用(计数+1)。

    我们再来看一下,在ARC下的几种情况:
    首先是捕获的变量是强引用。

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            {
                void (^gloadBlockC)(void);
                {
                    TestObject *testObj = [[TestObject alloc] init];
                    testObj.age = 3;
                    
                    __strong TestObject *weakTest = testObj;
    
                    gloadBlockC = ^void(){
                        NSLog(@"%d",weakTest.age);
                    } ;
                    
                    testObj.age = 5;
                }
                gloadBlockC();
            }
            
            NSLog(@"执行了");
        }
        return 0;
    }
    
    

    __strong是默认引用类型,不添加__strong也可以。 转换出来的c++代码如下:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      TestObject *__strong weakTest;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, TestObject *__strong _weakTest, int flags=0) : weakTest(_weakTest) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    我们可以看到,捕获到的auto变量类型也是__strong,经测试验证得出结论:在ARC环境下,block捕获的对象修饰符(__weak,__strong,__unsafe_unretained),与该对象本身的修饰符一致!


    在block进行copy到堆上的时候,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。
    当block从堆上移除的时候,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量(release)


    2 - __block修饰符

    我们先看一下下面的代码

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            {
                void (^gloadBlockC)(void);
                {
                    TestObject *testObj = [[TestObject alloc] init];
                    testObj.age = 3;
                    
                    TestObject *weakTest = testObj;
    
                    static int a = 1;
                    auto int b = 1;
                    
                    gloadBlockC = ^void(){
                        a = 2;
                        b = 2;
                        weakTest = [TestObject new];
                        NSLog(@"%d",weakTest.age);
                    };
                    
                    testObj.age = 5;
                }
                gloadBlockC();
            }
            
            NSLog(@"执行了");
        }
        return 0;
    }
    

    编译出现2处错误:Variable is not assignable (missing __block type specifier)。这里有个知识点,auto修饰的变量,在block中不能修改。那么,要在block中修改局部变量怎么办呢,除了static,我们还可以用__block来修饰。
    修改之后如下:

                    __block TestObject *weakTest = testObj;
    
                    static int a = 1;
                    auto __block int b = 1;
                    
                    gloadBlockC = ^void(){
                        a = 2;
                        b = 2;
                        weakTest = [TestObject new];
                        NSLog(@"%d",weakTest.age);
                    };
    

    那么,__block到底是什么呢?我们来看一下编译成c++的源码吧。


    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            {
                void (*gloadBlockC)(void);
                {
                    TestObject *testObj = ((TestObject *(*)(id, SEL))(void *)objc_msgSend)((id)((TestObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestObject"), sel_registerName("alloc")), sel_registerName("init"));
                    ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)testObj, sel_registerName("setAge:"), 3);
    
                    __attribute__((__blocks__(byref))) __Block_byref_weakTest_0 weakTest = {(void*)0,(__Block_byref_weakTest_0 *)&weakTest, 33554432, sizeof(__Block_byref_weakTest_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, testObj};
    
                    static int a = 1;
                    auto __attribute__((__blocks__(byref))) __Block_byref_b_1 b = {(void*)0,(__Block_byref_b_1 *)&b, 0, sizeof(__Block_byref_b_1), 1};
    
                    gloadBlockC = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a, (__Block_byref_b_1 *)&b, (__Block_byref_weakTest_0 *)&weakTest, 570425344));
    
                    ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)testObj, sel_registerName("setAge:"), 5);
                }
                ((void (*)(__block_impl *))((__block_impl *)gloadBlockC)->FuncPtr)((__block_impl *)gloadBlockC);
            }
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_swf2m0ds52l_g_ly5nz8vxmw0000gn_T_main_44cc4d_mi_2);
        }
        return 0;
    }
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int *a;
      __Block_byref_b_1 *b; // by ref
      __Block_byref_weakTest_0 *weakTest; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, __Block_byref_b_1 *_b, __Block_byref_weakTest_0 *_weakTest, int flags=0) : a(_a), b(_b->__forwarding), weakTest(_weakTest->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    struct __Block_byref_weakTest_0 {
      void *__isa;
    __Block_byref_weakTest_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     TestObject *__strong weakTest;
    };
    struct __Block_byref_b_1 {
      void *__isa;
    __Block_byref_b_1 *__forwarding;
     int __flags;
     int __size;
     int b;
    };
    

    可以看到,捕获到的变量bweakTest是一个结构体。关于__block有一下几个结论:

    • __block可以用于解决block内部无法修改auto变量值的问题
    • __block不能修饰全局变量、静态变量(static)
    • 当block在栈上时,并不会对__block变量产生强引用(同上文中描述的,栈上的block,不会对捕获的auto变量产生强引用) ,同上述,block从栈上拷贝到堆上时,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(retain)。
    • __block结构体中包含的变量,__weak,__strong,__unsafe_unretained修饰符与捕获变量本身一致。

    3 - block的循环引用

    在ARC环境下:先看下下面的代码

    
    #import <Foundation/Foundation.h>
    
    @interface TestObject : NSObject
    
    @property (nonatomic , assign) int age;
    
    @property (strong , nonatomic) void(^testBlock)(void);
    
    @end
    
    @implementation TestObject
    
    - (void)test{
        self.age = 5;
        TestObject *weakSelf = self;
        self.testBlock = ^{
            NSLog(@"%d",weakSelf.age);
        };
        self.testBlock();
    }
    
    - (void)dealloc {
        NSLog(@"%s",__func__);
    }
    
    @end
    
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            {
                [[[TestObject alloc] init] test];
            }
            
            NSLog(@"执行了");
        }
        return 0;
    }
    

    输出结果为:

    5
    执行了

    TestObject对象没有被释放。
    造成这个问题的原因是TestObject持有了一个testBlock属性,testBlock中又捕获了TestObject对象本身,都是强引用,导致循环引用,无法释放。
    所以,解决循环引用的原理就是,打破互相强引用。简单的做法,我们将block引用的weakself改成弱引用。

        __weak TestObject *weakSelf = self;
    

    这样,块代码捕获到的对象就成了弱引用。如下:

    struct __TestObject__test_block_impl_0 {
      struct __block_impl impl;
      struct __TestObject__test_block_desc_0* Desc;
      TestObject *__weak weakSelf;
      __TestObject__test_block_impl_0(void *fp, struct __TestObject__test_block_desc_0 *desc, TestObject *__weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    所以,当weakSelf被释放的时候,weakSelf中的testBlock就被释放了。
    如图:


    image.png

    还有一种是使用__block来修饰weakSelf。将weakSelf包一层,要释放的时候,将block中捕获的强引用赋值为nil,或者将对象的block赋值为nil,打破循环的强引用就可以。
    如图:

    image.png

    还可以用 __unsafe_unretained来修饰weakSelf。用__unsafe_unretained来修饰的话,block内部copy的时候,不会对捕获的变量进行retain操作(也就是计数器+1,也就是强引用)。

        __unsafe_unretained TestObject *weakSelf = self;
    

    在MRC环境下:

    同理,我们可以用__unsafe_unretained来修饰。

    我们可以用

        __unsafe_unretained TestObject *weakSelf = self;
    

    另外,我们可以用__block来修饰weakSelf。只不过这里跟ARC环境不一样的是,在MRC下,block的copy到堆的时候,_Block_object_assign 不会对捕获到的变量根据修饰类型形成强引用!!!

        __block TestObject *weakSelf = self;
    

    总结一下:

    在block进行copy到堆上的时候,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。
    (仅限于ARC环境!MRC不会retain操作! 仅限于ARC环境!MRC不会retain操作! 仅限于ARC环境!MRC不会retain操作! 仅限于ARC环境!MRC不会retain操作! 仅限于ARC环境!MRC不会retain操作!)

    相关文章

      网友评论

          本文标题:02 - Block(内存管理篇)

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