文档章节

iOS容易造成循环引用的三种场景之Block以及对应的使用方法(二)

super_co
 super_co
发布于 2015/06/12 16:35
字数 1975
阅读 25
收藏 0

一  、Block的内存泄露体现

block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题。

一般表现为:

1.某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,简单说就是

self.someBlock = ^(Type var){

[self dosomething];

self.otherVar = XXX;

或者_otherVar = ...

};

block的这种循环引用会被编译器捕捉到并及时提醒。(这个还是很人性化的,难道你不这么觉得吗?0.0)

     list:

//  Created by 58 on 15/6/3.

//  Copyright (c) 2015年 58. All rights reserved.

//


#import"TsetBlock.h"

void (^blockTest)(NSString *str,NSString *st );

typedefvoid (^blockT)(NSString *str,NSString *st );


@interface TsetBlock ()

@property (nonatomic)NSArray *testArr;

@property (nonatomic ,copy)blockT block;

@end



@implementation TsetBlock


-(id)init{


   if (self = [superinit]) {

       self.testArr =@[@"你",@"觉",@"的",@"会",@"怎",@"样"];

        self.block = ^(NSString *name,NSString *str){

           NSLog(@"arr:%@",self.testArr);

       };

   }

   returnself;

}

@end


那么问题来了:

网上大部分帖子都表述为"block里面引用了self导致循环引用",但事实真的是如此吗?我表示怀疑,其实这种说法是不严谨的,不一定要显式地出现"self"字眼才会引起循环引用。我们改一下代码,不通过属性self.arr去访问arr变量,而是通过实例变量_arr去访问,如下:

很明显了:

即使在你的block代码中没有显式地出现"self",也会出现循环引用!只要你在block里用到了self所拥有的东西!但对于这种情况,目前我不知道该如何排除掉循环引用,因为我们无法通过加__weak声明或者__block声明去禁止block对self进行强引用或者强制增加引用计数。对于self.arr的情况,我们要分两种环境去解决:

1.arc:  __weaktypeof(self) weakSelf=self; 其实 __weak someClass *weakSelf = self也是OK的!!!

2.MRC:解决方式与上述基本一致,只不过将__weak关键字换成__block即可,这样的意思是告诉block:孙子,咱们已经没有关系了(不要在内部对self进行retain了)!

 二、正确的使用BLOCK避免cycle retain

  我们一起来看看,经Clang编译后的block结构

struct Block_literal_1 {      void *isa; 
      int flags;      int reserved;      void (*invoke)(void *, ...);      struct Block_descriptor_1 {      unsigned long int reserved;       
          unsigned long int size;      
          // optional helper functions
         void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
         void (*dispose_helper)(void *src);             // IFF (1<<25)         // required ABI.2014.5.25
         const char *signature;                         // IFF (1<<30)
     } *descriptor;     // imported variables
 };


  1. 1.Block执行的代码,这是在编译的时候已经生成好的;

  2. 2.一个包含Block执行时需要的所有外部变量值的数据结构。 Block将使用到的、作用域附近到的变量的值建立一份快照拷贝到栈上。

  3. Block与函数另一个不同是,Block类似ObjC的对象,可以使用自动释放池管理内存(但Block并不完全等同于ObjC对象,后面将详细说明)。

  4. 关于Block的基本语法就不在陈述,相信都已经滚瓜烂熟了

  5. ...

  6. (1)block在内存中的位置

  7.          根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock。

    • <1>NSGlobalBlock:类似函数,位于text段;

    • <2>NSStackBlock:位于栈内存,函数返回后Block将无效;

    • <3>NSMallocBlock:位于堆内存。

         可以看到在Block结构体中含有isa指针,这就证明了Block其实就是对象,并具有一般对象的所有功能。这个isa指针被初始化为_NSConcreteStackBlock或者_NSConcreteGlobalBlock类的地址。在没有开启ARC的情况下,如果Block中包含有局部变量则isa被初始化为前者,否则就被初始化为后者。而当ARC开启后,如果Block中包含有局部变量则isa被初始化为_NSConcreteMallocBlock,否则就被初始化为_NSConcreteGlobalBlock。invoke是一个函数指针,它指向的是Block被转换成函数的地址。最后的imported variables部分是Block需要访问的外部的局部变量,他们在编译就会被拷贝到Block中,这样一来Block就是成为一个闭包了。

     代码LIST:

   1>   blockT blk1 =   ^ int(int a, int b) {

           return a+b ;

        };

(lldb)po blk1

<__NSGlobalBlock__: 0x102b54080>

        

        

    2>    int base =100;

        blockT blk2 =   ^ int(int a,int b) {

            return base +a+b ;

        };

        

(lldb)po blk2

<__NSStackBlock__: 0x7fff5d0abab0>

        

     3>   blockT blk3 = [[blk2copyautorelease];

(lldb)po blk3

<__NSMallocBlock__: 0x7f89aa6854f0>

   在Block内变量base是只读的,如果想在Block内改变base的值,在定义base时要用 __block修饰


 Block的copy、retain、release操作

  • 1) Block_copy与copy等效,Block_release与release等效;


  • 2) 对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;

  •         3) NSGlobalBlock:retain、copy、release操作都无效;

  • retain cycle

    retain cycle问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致了retain cycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。

    ASIHTTPRequest*request=[ASIHTTPRequestrequestWithURL:url];

    • 4) NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry addObject:stackBlock],在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。

    • 5)NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;

    • 6) 尽量不要对Block使用retain操作。

  • [requestsetCompletionBlock:^{ 

  • NSString*string=[requestresponseString];

  • }];


       +-----------+           +-----------+       | request   |           |   Block   |  ---> |           | --------> |           |       | retain 2  | <-------- | retain 1  |       |           |           |           |       +-----------+           +-----------+


要想打破循环可以  __block ASIHTTPRequest*request=[ASIHTTPRequestrequestWithURL:url];



      +-----------+           +-----------+      | request   |           |   Block   | ---->|           | --------> |           |      | retain 1  | < - - - - | retain 1  |      |           |   weak    |           |      +-----------+           +-----------+

request被持有者释放后。request 的retainCount变成0,request被dealloc,request释放持有的Block,导致Block的retainCount变成0,也被销毁。这样这两个对象内存都被回收。

retain cycle不只发生在两个对象之间,也可能发生在多个对象之间,这样问题更复杂,更难发现


ClassA* objA = [[[ClassA alloc] init] autorelease];  objA.myBlock = ^{    [self doSomething];  };  self.objA = objA;

  +-----------+           +-----------+           +-----------+  |   self    |           |   objA    |           |   Block   |  |           | --------> |           | --------> |           |  | retain 1  |           | retain 1  |           | retain 1  |  |           |           |           |           |           |  +-----------+           +-----------+           +-----------+       ^                                                |       |                                                |       +------------------------------------------------+

解决办法同样是用__block打破循环引用


ClassA* objA = [[[ClassA alloc] init] autorelease];MyClass* weakSelf = self;objA.myBlock = ^{  [weakSelf doSomething];};self.objA = objA;

注意:MRC中__block是不会引起retain;但在ARC中__block则会引起retain。ARC中应该使用__weak__unsafe_unretained弱引用。__weak只能在iOS5以后使用。

Block使用对象被提前释放

看下面例子,有这种情况,如果不只是request持有了Block,另一个对象也持有了Block。


      +-----------+           +-----------+      | request   |           |   Block   |   objA ---->|           | --------> |           |<--------      | retain 1  | < - - - - | retain 2  |      |           |   weak    |           |      +-----------+           +-----------+

这时如果request 被持有者释放。


      +-----------+           +-----------+      | request   |           |   Block   |   objA --X->|           | --------> |           |<--------      | retain 0  | < - - - - | retain 1  |      |           |   weak    |           |      +-----------+           +-----------+

这时request已被完全释放,但Block仍被objA持有,没有释放,如果这时触发了Block,在Block中将访问已经销毁的request,这将导致程序crash。为了避免这种情况,开发者必须要注意对象和Block的生命周期。

另一个常见错误使用是,开发者担心retain cycle错误的使用__block。比如


__block kkProducView* weakSelf = self;dispatch_async(dispatch_get_main_queue(), ^{  weakSelf.xx = xx;});

将Block作为参数传给dispatch_async时,系统会将Block拷贝到堆上,如果Block中使用了实例变量,还将retain self,因为dispatch_async并不知道self会在什么时候被释放,为了确保系统调度执行Block中的任务时self没有被意外释放掉,dispatch_async必须自己retain一次self,任务完成后再release self。但这里使用__block,使dispatch_async没有增加self的引用计数,这使得在系统在调度执行Block之前,self可能已被销毁,但系统并不知道这个情况,导致Block被调度执行时self已经被释放导致crash。


// MyClass.m- (void) test {  __block MyClass* weakSelf = self;  double delayInSeconds = 10.0;  dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));  dispatch_after(popTime, dispatch_get_main_queue(), ^(void){    NSLog(@"%@", weakSelf);});// other.mMyClass* obj = [[[MyClass alloc] init] autorelease];[obj test];


© 著作权归作者所有

共有 人打赏支持
super_co
粉丝 1
博文 10
码字总数 7205
作品 1
东城
高级程序员
iOS奇思妙想之使用block替代通知

前言 iOS开发中,很多情况下会使用到通知,通知的好处很多,但是也有很多坑点,一旦没有管理好,就会造成很多莫名其妙的bug。既然通知使用不当很容易出现问题,那有没有什么办法来避免?经过...

季末微夏
08/17
0
0
IOS 浅谈闭包block的使用

前言:对于ios初学者,block通常用于逆向传值,遍历等,会使用,但是可能心虚,会感觉block很神秘,那么下面就一起来揭开它的面纱吧。 ps: 下面重点讲叙了闭包的概念,常用的语法,以及访问变...

周雨奇
07/23
0
0
iOS源码补完计划--AFNetworking 3.1.0源码研读

参拜一下AFNetworking的源码。 第四篇源码、暂时来看也是iOS方向的最后一篇、撸完准备趁着热乎撸一撸网络协议。 目录 准备工作 功能模块 AFURLSessionManager/AFHTTPSessionManager AFNetwo...

kirito_song
05/25
0
0
iOS与JS交互之UIWebView-JavaScriptCore框架

级别:★★☆☆☆ 标签:「iOS与JS交互」「UIWebView与JS交互」「JavaScriptCore」 作者: Xs·H 审校: QiShare团队 先解释下标题:“iOS与JS交互”。iOS指原生代码(文章只有示例),JS指前...

QiShare
08/30
0
0
iOS查看屏幕帧数工具--YYFPSLabel

学习 YYKit 代码时,发现 ibireme 在项目里加入的一个查看当前屏幕帧数的小工具,效果如下: 挺实用,实现方法也很简单,但是思路特别棒。 这里是Demo: YYFPSLabel 这里我把这个小工具从 中...

yehot
2016/04/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

你为什么在Redis里读到了本应过期的数据

一个事故的故事 晚上睡的正香突然被电话吵醒,对面是开发焦急的声音:我们的程序在访问redis的时候读到了本应过期的key导致整个业务逻辑出了问题,需要马上解决。 看到这里你可能会想:这是不...

IT--小哥
今天
2
0
祝大家节日快乐,阖家幸福! centos GnuTLS 漏洞

yum update -y gnutls 修复了GnuTLS 漏洞。更新到最新 gnutls.x86_64 0:2.12.23-22.el6 版本

yizhichao
昨天
5
0
Scrapy 1.5.0之选择器

构造选择器 Scrapy选择器是通过文本(Text)或 TextResponse 对象构造的 Selector 类的实例。 它根据输入类型自动选择最佳的解析规则(XML vs HTML): >>> from scrapy.selector import Sele...

Eappo_Geng
昨天
4
0
Windows下Git多账号配置,同一电脑多个ssh-key的管理

Windows下Git多账号配置,同一电脑多个ssh-key的管理   这一篇文章是对上一篇文章《Git-TortoiseGit完整配置流程》的拓展,所以需要对上一篇文章有所了解,当然直接往下看也可以,其中也有...

morpheusWB
昨天
5
0
中秋快乐!!!

HiBlock
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部