美文网首页
Block 内存管理

Block 内存管理

作者: Heikki_ | 来源:发表于2018-03-03 09:08 被阅读72次

    block 为什么用 copy 修饰

    内存栈区

    由编译器自动分配释放,存放函数的参数值,局部变量的值等,不需要程序员来操心。其操作方式类似于数据结构中的栈。

    内存堆区

    一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。尽管后边苹果引入了ARC机制,但是ARC的机制其实仅仅是系统帮助程序员添加了retain,release,autorelease代码,并不是说系统就可以自动管理了。他的系统管理的原理还是MRC,并没有本质区别。注意内存堆区与数据结构中的堆是两回事,分配方式倒是类似于链表。

    block的作用域

    block是一个对象,所以block理论上是可以retain/release的。但是block在创建的时候它的内存是默认是分配在栈(stack)上,而不是堆(heap)上的。所以它的作用域仅限创建时候的当前上下文(函数, 方法...),当你在该作用域外调用该block时,block占用的内存已经释放,无法进行访问,程序就会崩溃,出现野指针错误。

    函数返回

    在一个函数的内部,return的时候返回的都是一个拷贝,不管是变量、对象还是指针都是返回拷贝,但是这个拷贝是浅拷贝。

    对于返回一些非动态分配(new/malloc)得到的指针就可能出现问题,因为尽管你返回了这个指针地址。但是这个指针可能指向的栈内存,栈内存在函数执行完毕后就自动销毁了。如果销毁之后你再去访问,就会访问坏内存会导致程序崩溃。
    在MRC下,如果一个block作为参数,没有经过copy就返回。后果是什么呢?由于return的时候返回的是浅拷贝,也就是说返回的是对象的地址,因为在返回后这个block对应的栈内存就销毁了。如果你多次调用这个block就会发现,程序会崩溃。崩溃原因就是上边所说,block占用的空间已经释放了,你不可以进行访问了。
    解决方案:就是在返回的时候,把block进行拷贝作为参数进行返回。这样做的好处是返回的那个block存储空间是在堆内,堆内的空间需要程序员自己去释放,系统不会自动回收,也就不会出现访问已释放内存导致的崩溃了。也就是我们在MRC下需要使用copy修饰符的原因。

    什么时候block会造成循环引用

    首先,并不是在 block 内部使用self 都会出现循环引用问题.
    只有block 直接或间接被 self 储存才会造成循环引用.

    在 block 中使用 self或 self 属性或者"_"语法都会使 block 对 self 进行强引用,因为只要block中用到了对象的属性或者函数,block就会持有该对象而不是该对象中的某个属性或者函数。

    实际开发中常见block的循环引用:

    使用通知(NSNotifation),调用系统自带的Block,在Block中使用self会发生循环引用。

    block 循环引用的解决办法:

    方法一
    通过__weak的修饰,先把self弱引用(默认是强引用,实际上self是有个隐藏的__strong修饰的),然后在block回调里用weakSelf,这样就会打破保留环,从而避免了循环引用

     __weak typeof(self) weakSelf = self;
    

    提醒:

    __block与__weak都可以用来解决循环引用,
    __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
    __weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
    __block对象可以在block中被重新赋值,__weak不可以。
    

    方法二
    @weakify

      @weakify(self)
      self.myBlock = ^() {
        NSString *localString = self.blockString;
      };
    

    缺陷

    以上两个方法都可以解决 block-self的循环引用问题,但是如果我想在Block中延时来运行某段代码,这里就会出现一个问题,看这段代码:

      - (void)viewDidLoad {
        [super viewDidLoad];
        MitPerson*person = [[MitPerson alloc]init];
        __weak MitPerson * weakPerson = person;
        person.mitBlock = ^{
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [weakPerson test];
            });
        };
        person.mitBlock();
      }
    

    直接运行这段代码会发现[weakPerson test];并没有执行,打印一下会发现,weakPerson已经是 Nil 了,这是由于当我们的viewDidLoad方法运行结束,由于是局部变量,无论是MitPerson和weakPerson都会被释放掉,那么这个时候在Block中就无法拿到正真的person内容了。

    解决办法 strong-weak-dance

      - (void)viewDidLoad {
        [super viewDidLoad];
        MitPerson*person = [[MitPerson alloc]init];
        __weak MitPerson * weakPerson = person;
        person.mitBlock = ^{
            __strong MitPerson * strongPerson = weakPerson;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [strongPerson test];
            });
        };
        person.mitBlock();
      }
    
    

    原理

    堆里面的block(被copy过的block)有以下现象:
    1.block内部如果通过外面声明的强引用来使用,那么block内部会自动产生一个强引用指向所使用的对象。
    2.block内部如果通过外面声明的弱引用来使用,那么block内部会自动产生一个弱引用指向所使用的对象。
    

    https://juejin.im/post/5a44ddd36fb9a045031065b2

    相关文章

      网友评论

          本文标题:Block 内存管理

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