美文网首页
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(内存管理篇)

    继续上面的基础篇内容。 1 - block的强引用与弱引用 我们先看一下这样的代码 在ARC环境下,输出结果是: ...

  • Block内存管理实例分析

    Block内存管理实例分析 Block内存管理实例分析

  • Block 内存管理(ARC)

    上篇文章介绍了MRC环境下,Block内存管理,紧接着此篇介绍ARC环境下Block的内存管理。 介绍之前我们需要...

  • iOS进阶——iOS(Objective-C)内存管理·二

    date: 2017-02-15 11:59:33 在写 『iOS(Objective-C) 内存管理&Block...

  • Block内存管理

    对block自身内存的管理 对于block,有两个内存管理方法:Block_copy, Block_release...

  • iOS内存管理详解

    目录 block内存管理 autorelease内存管理 weak对象内存管理 NSString内存管理 new、...

  • Block 使用总结

    Block 内存管理: Block 内存主要分派到 NSGlobalBlock(data area),NSMall...

  • iOS面试之Block大全

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS面试之Block模块

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • Block

    一、Block本质 二、 BlocK截获变量 三、__block 修饰变量 四、Block内存管理 五、Block...

网友评论

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

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