iOS Block(4)-block内存管理,block循环引用

1. Block内存管理

iOS Block(4)-block内存管理,block循环引用_第1张图片
demo代码.png

OC代码转换成C++代码

void(*block)(void);

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref

            (age->__forwarding->age) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_3fd458_mi_0,(age->__forwarding->age));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};

        block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

_block的内部要调用外边的变量,_block的desc0的结构体里会多出copydispose函数方法,copy的函数方法中会调用_Block_object_assign进行内存管理.

  1. 当block在栈上时,并不会对__block变量产生强引用.
  2. 当block被copy到堆时
        ①. 会调用block内部的copy函数.
        ②. copy函数内部会调用_Block_object_assign函数.
        ③. _Block_object_assign函数会对__block变量形成强引用(retain).
iOS Block(4)-block内存管理,block循环引用_第2张图片
copy操作第一步.png
iOS Block(4)-block内存管理,block循环引用_第3张图片
copy操作第二步.png

当block从堆中移除时

  • 会调用block内部的dispose函数
  • dispose函数内部会调用_Block_object_dispose函数
  • _Block_object_dispose函数会自动释放引用的__block变量(release)
iOS Block(4)-block内存管理,block循环引用_第4张图片
dispose操作的第一步.png
iOS Block(4)-block内存管理,block循环引用_第5张图片
dispose操作的第二步.png

对象类型的auto变量、__block变量

  1. 当block在栈上时,对它们都不会产生强引用.
  2. 当block拷贝到堆上时,都会通过copy函数来处理它们.
  • __block变量(假设变量名叫做a)
    _Block_object_assign((void*)&dst->a, (void*)src->a,8/*BLOCK_FIELD_IS_BYREF*/);
  • 对象类型的auto变量(假设变量名叫做p)
    _Block_object_assign((void*)&dst->p, (void*)src->p,3/*BLOCK_FIELD_IS_OBJECT*/);
  1. 当block从堆上移除时,都会通过dispose函数来释放它们
  • __block变量(假设变量名叫做a)
    _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
  • 对象类型的auto变量(假设变量名叫做p)
    _Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
_block变量.png

2. __block的__forwarding指针

iOS Block(4)-block内存管理,block循环引用_第6张图片
_forwarding指针的示意图.jpg

用"__block"修饰auto变量xxx的时候,系统会将这个auto变量xxx转换成一个__Block_byref_xxx_0结构体类型,结构体中有个成员__forwarding。当block在栈区的时候,__forwarding指向栈区的__Block_byref_xxx_0结构体本身内存地址;当block被copy到堆区的时候,栈上block变量内的__forwarding将会指向堆上的block变量,从而进一步访问block变量内部的成员。这样,前文中访问age的时候通过" (age->__forwarding->age) = 20;"这种做法也就明白了。

3. __block修饰对象类型

  • block内部的指针指向包装好的结构体就是强指针,没有弱引用.结构体内部的对象指向外部的变量的指针是强指针还是弱指针是由外部的变量的修饰词决定的.
  • 如果__block修饰的是对象类型,block从栈区copy到堆区的时候,包装好的对象会增加copy和dispose两个函数对包装好的对象做内存管理.
  1. __block变量在栈上时,不会对指向的对象产生强引用
  2. __block变量被copy到堆时
       ①. 会调用__block变量内部的copy函数
       ②. copy函数内部会调用_Block_object_assign函数
       ③._Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain
  3. 如果__block变量从堆上移除
       ①. 会调用__block变量内部的dispose函数
       ②. dispose函数内部会调用_Block_object_dispose函数
       ③. _Block_object_dispose函数会自动释放指向的对象(release)

4. block循环引用

一. demo1

iOS Block(4)-block内存管理,block循环引用_第7张图片
self的循环引用的常例.png

demo1中,TestClass有一个block实例对象,self对block的关系为强持有。block实现中,也引用了当前实例self,并且也为强引用。这样一来,self持有block,block持有self,所以两者都无法释放,就造成内存泄露。将该.m文件转换为C++实现,看看block结构体__TestClass__test_block_impl_0和block代码块函数__TestClass__test_block_func_0:

struct __TestClass__test_block_impl_0 {
  struct __block_impl impl;
  struct __TestClass__test_block_desc_0* Desc;
  TestClass *const __strong self;
  __TestClass__test_block_impl_0(void *fp, struct __TestClass__test_block_desc_0 *desc, TestClass *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __TestClass__test_block_func_0(struct __TestClass__test_block_impl_0 *__cself) {
  TestClass *const __strong self = __cself->self; // bound by copy


        NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_TestClass_e9b143_mi_0, ((NSInteger (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("age")));
    }

正如上面分析的一样:由"TestClass *const __strong self;"可见block结构体中成员变量self为当前类实例的强指针;并且block的代码块中TestClass *const __strong self = __cself->self; // bound by copy也强引用着当前类TestClass的实例.
block与self的互相持有:

iOS Block(4)-block内存管理,block循环引用_第8张图片
block与self的互相持有.png

二. demo2

#import 
typedef void(^CSBlock)(void);
@interface Person : NSObject
/** age*/
@property(nonatomic,assign)int age;
/** blokc*/
@property(nonatomic,copy) CSBlock block;
@end

@implementation Person
- (void)dealloc {
    NSLog(@"%s",__func__);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
          Person *person = [[Person alloc] init];
          person.age = 10;
          person.block = ^{
              NSLog(@"age is %d", person.age);
  };
      person.block();
  }
     return 0;
}

将上方的OC代码转换为C++:

iOS Block(4)-block内存管理,block循环引用_第9张图片
Person循环引用OC->C++.png

person的循环引用形成

iOS Block(4)-block内存管理,block循环引用_第10张图片
person的循环引用形成.png

block内部有个强指针person指向MJPerson,MJPerson有个成员变量_block指向block.

iOS Block(4)-block内存管理,block循环引用_第11张图片
person的循环引用1.png

当我们执行完main函数中的20行代码的时候,main中的person指向MJPerson的指针就销毁了.

iOS Block(4)-block内存管理,block循环引用_第12张图片
person的循环引用2.png

三. 解决block与持有对象间的强引用关系.

在ARC环境下有以下三种解决方案:
① 使用"__weak";
② 使用"__unsafe_unretained";
③ 使用"__block"(必须要调用block)。

方案①大家应该都清楚,在开发的过程中都应该使用过.
方案②:“__unsafe_unretained”字面理解就是不安全的、不会导致引用计数增加。简单说就是:不安全的弱引用。
“__weak”与“ __unsafe_unretained”对比:
"__weak":不会产生强引用,当指向的对象销毁时,会自动让指针置为nil;
“ __unsafe_unretained”:不会产生强引用,不安全。当指向的对象销毁时,指针存储的地址值不变,这个时候指向的是一块已经被系统回收的内存,这个时候继续访问会引发"野指针异常"。
对于方案③demo:

#import 
#import "TestClass.h"

@implementation TestClass

- (void)test{
    
    self.age = 20;
    
//    __unsafe_unretained TestClass *weakself = self;
//    __weak TestClass *weakself = self;
    
    __block TestClass* weakSelf = self;
    
    self.block = ^{
        
        NSLog(@"%ld", weakSelf.age);
        weakSelf = nil;
    };
    self.block();

}

- (void)dealloc{
    NSLog(@"TestClass - %@",NSStringFromSelector(_cmd));
}

当testClass实例销毁的时候,block也释放了,不会循环引用。
我们分析一下转换后的C++代码:

struct __TestClass__test_block_impl_0 {
  struct __block_impl impl;
  struct __TestClass__test_block_desc_0* Desc;
  __Block_byref_weakSelf_0 *weakSelf; // by ref
  __TestClass__test_block_impl_0(void *fp, struct __TestClass__test_block_desc_0 *desc, __Block_byref_weakSelf_0 *_weakSelf, int flags=0) : weakSelf(_weakSelf->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


struct __Block_byref_weakSelf_0 {
  void *__isa;
__Block_byref_weakSelf_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 TestClass *__strong weakSelf;
};

首先block结构体内持有__block类型的"__Block_byref_weakSelf_0"对象;
其次“__block”类型对象中“TestClass *__strong weakSelf;”,即持有TestClass实例。
由于self持有了block,所以当前对象self、block已经__block变量三者的关系为:

iOS Block(4)-block内存管理,block循环引用_第13张图片
三者关系的循环引用.png

如此一来:又是一个循环引用问题,我们尝试在block代码块内部去掉"weakSelf = nil",实际结果是TestClass实例不会释放掉。针对这种状况,打破三者之间的循环链即可消除循环引用,解释如下:
首先a. 对象(也就是持有block的对象)对block的持有关系肯定是强持有;
其次b. block对__block变量也是强持有的关系,这两条线无法改动!如果突破__block变量持有对象这条线,就可以了,这样就可以通过调用block后,手动设置__block对象为nil。

在本demo中__block变量定义如下:

struct __Block_byref_weakSelf_0 {
  void *__isa;
__Block_byref_weakSelf_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 TestClass *__strong weakSelf;
};

也就是将结构体__Block_byref_weakSelf_0中的成员变量"TestClass *__strong weakSelf"置为nil,即weakSelf = nil,这样__block就不会持有当前类的实例了,所以循环被打破。打破后三者关系见下图:

iOS Block(4)-block内存管理,block循环引用_第14张图片
打破三种的循环引用关系.png

由此针对方案③:该方案唯一的缺点就是需要执行block。这么麻烦的关键在于:执行完block之后,在block体内设置引用对象为nil,从而达到手动将__block变量内部的关键成员置为nil,这样就可以打破循环关系.

在ARC环境下有以下三种解决方案:
① 用__unsafe_unretained解决;
② 用__block解决(block可以不调用)。

__unsafe_unretained”同ARC一致;
在使用"_block"时,我们先总结一下block被copy到堆上时,底层做了啥(⊙⊙)?

当__block变量被copy到堆时,会调用__block变量内部的copy函数。copy函数内部会调用_Block_object_assign函数。_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)。
如果__block变量从堆上移除,会调用__block变量内部的dispose函数。dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放指向的对象(release)。

正是因为MRC环境下,__block变量对所引用的对象为弱引用关系,所以“对象”、“block”与"block变量"三种之间处于开环状态,也就不存在循环引用问题,因此在MRC下用__block修饰被引用对象,block可以不调用。正如下面demo:

// ATTENTION:MRC环境
#import 
#import "TestClass.h"

@implementation TestClass

- (void)test{
    
    self.age = 20;
    
    __block TestClass* weakSelf = self;
    
    self.block = ^{
        
        NSLog(@"%ld", weakSelf.age);
    };
}

- (void)dealloc{
    [super dealloc];
    NSLog(@"TestClass - %@",NSStringFromSelector(_cmd));
}

@end


int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        {
            TestClass *testClass = [[TestClass alloc] init];
           
            [testClass test];
            [testClass release];
            
        }
        NSLog(@"---------");
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}


//2018-08-31 11:25:27.800865+0800 Block[74957:7622148] TestClass - dealloc
//2018-08-31  11:25:27.803116+0800 Block[74957:7622148] ---------

结果是TestClass实例被销毁的时候,block也一起销毁了。

四. __weak搭配__strong使用

__weak TestClass* weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%ld", strongSelf.age);
    };

在本demo中,在block内部重新使用__strong修饰weakSelf(被引用)变量是为了在block内部有一个强指针指向weakSelf(弱引用)避免在block调用的时候weakSelf已经被销毁。有些时候block内部访问的对象并不是当前类的实例,考虑到block可能很久才会销毁,因此被block引用的对象应该是弱引用,否则可能造成被引用对象毫无意义地存在于内存中。既然是弱引用,一旦该对象在其他地方被销毁,则block内部的弱引用对象也就销毁了,继续访问也就会返回null,还是用demo说话吧:

// 1. 新建一个Dog类,并实现dealloc方法;
@interface Dog : NSObject

@property (nonatomic, copy) NSString *name;

@end

@implementation Dog

- (void)dealloc{
  
    NSLog(@"Dog - %@",NSStringFromSelector(_cmd));
}


// 2. 在另一个类的block函数体类访问dog的成员属性name;
#import 
#import "TestClass.h"
#import "Dog.h"

@implementation TestClass

- (void)test{
    
    Dog *dog = [[Dog alloc] init];
    dog.name = @"小黑";

    __weak Dog *weakDog = dog;
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"开始执行block");
//        __strong typeof(weakDog) strongDog = weakDog;
        sleep(2);
        NSLog(@"狗的名字:%@",weakDog.name);
    });
  
    sleep(1);
    NSLog(@"模拟weakDog被释放");
    dog = nil;
    
    /*
    2018-09-04 14:43:32.966624+0800 Block[80253:7808973] 开始执行block
    2018-09-04 14:43:33.956807+0800 Block[80253:7808911] 模拟weakDog被释放
    2018-09-04 14:43:33.957256+0800 Block[80253:7808911] Dog - dealloc
    2018-09-04 14:43:34.972230+0800 Block[80253:7808973] 狗的名字:(null)
    */
}

- (void)dealloc{
    NSLog(@"TestClass - %@",NSStringFromSelector(_cmd));
}

@end

模拟block内部访问的对象在外部被提前释放的情况,我在调用block的过程中特意将dog设置为nil,访问的结果是:“Block[76269:7674002] 狗的名字:(null)”,项目中block调用的时机是不确定的,被访问的对象何时候释放也是不确定的,故而这种情况下仅仅使用__weak修饰被访问对象肯定存在问题,为了更好解决这样的问题,我们用“__strong”修饰符在block内部搭配外部的"__weak"修饰被访问对象,针对上面demo,正确的做法如下:

- (void)test{
    
    Dog *dog = [[Dog alloc] init];
    dog.name = @"小黑";

    __weak Dog *weakDog = dog;
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"开始执行block");
        __strong typeof(weakDog) strongDog = weakDog;
        sleep(2);
        NSLog(@"狗的名字:%@",strongDog.name);
    });
  
    sleep(1);
    NSLog(@"模拟weakDog被释放");
    dog = nil;
    /*
     2018-09-04 14:46:32.969188+0800 Block[80345:7811829] 开始执行block
     2018-09-04 14:46:33.961744+0800 Block[80345:7811757] 模拟weakDog被释放
     2018-09-04 14:46:33.962013+0800 Block[80345:7811757] ---------
     2018-09-04 14:46:34.974592+0800 Block[80345:7811829] 狗的名字:小黑
     2018-09-04 14:46:34.974973+0800 Block[80345:7811829] Dog - dealloc
    */
}

在block内将weakDog对象强引用为strongDog,执行block过程中将dog设置为nil,结果仍能继续访问。

                            想了解更多iOS学习知识请联系:QQ(814299221)

你可能感兴趣的:(iOS Block(4)-block内存管理,block循环引用)