ARC下面的Block对内存的管理方式

一、问题引入

  近日开发中引入一个随机crash,Crash堆栈如下:

  

Exception Type: SIGSEGV
Exception Codes: SEGV_ACCERR at 0x0000000101850148
Crashed Thread: 0

Thread 0 Crashed:
0  libobjc.A.dylib                0x00000001802601a0 objc_retain + 16
1  CoreFoundation                 0x0000000180f593a0 -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:] +  232
2  LiveAssistant                  0x0000000100213fe8 -[LAPKViewStatusObj notifyViewStateDidChangeForType:fromUser:] (LAPKViewStatusObj.m:313)
3  LiveAssistant                  0x0000000100214958 -[LAPKViewStatusObj changePKStatusTo:changeMatchStatusTo:changeGuestMatchStatusTo:] (LAPKViewStatusObj.m:635)
4  LiveAssistant                  0x0000000100213ed4 -[LAPKViewStatusObj updatePKState] (LAPKViewStatusObj.m:300)
5  CoreFoundation                 0x000000018101cc3c ___CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ +  20
 +  20
6  CoreFoundation                 0x000000018101c1b8 __CFXRegistrationPost +  428
7  CoreFoundation                 0x000000018101bf14 ____CFXNotificationPost_block_invoke +  216
8  CoreFoundation                 0x000000018109984c -[_CFXNotificationRegistrar find:object:observer:enumerator:] +  1408
9  CoreFoundation                 0x0000000180f52f38 _CFXNotificationPost + 376
10 Foundation                     0x00000001819c3bbc -[NSNotificationCenter postNotificationName:object:userInfo:] +  68
11 LiveAssistant                  0x00000001003ae710 __44-[LAPKStatusManager notifyPKStatusDidChange]_block_invoke (LAPKStatusManager.m:107)
12 libdispatch.dylib              0x000000018097caa0 __dispatch_call_block_and_release +  24
13 libdispatch.dylib              0x000000018097ca60 __dispatch_client_callout +  16
14 libdispatch.dylib              0x000000018098965c __dispatch_main_queue_callback_4CF$VARIANT$mp +  1012
15 CoreFoundation                 0x0000000181033070 ___CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ +  12
 +  12
16 CoreFoundation                 0x0000000181030bc8 ___CFRunLoopRun +  2272
17 CoreFoundation                 0x0000000180f50da8 CFRunLoopRunSpecific + 544
18 GraphicsServices               0x0000000182f36020 GSEventRunModal + 100
19 UIKit                          0x000000018af70758 UIApplicationMain + 228
20 LiveAssistant                  0x000000010048b514 main (main.m:14)
21 libdyld.dylib                  0x00000001809e1fc0 _start +  4

  明显是对一个对象进行retain的时候产生的Crash。仔细回忆却没有发现突破点。直到看到自己写的下列代码

- (void)xxxxxBlock:(someBlock)block
                      yyyy:(NSString *)zzzzz
{

    __block someblock copyUserBlock = [block copy];
    if(block)
    {
        __weak typeof(self) wself = self;
        someblock hook = ^(NSObject *statusObj, NSObject *model, int status, NSString *businessKey) {
            __strong typeof(self) sself = wself;
            block(statusObj, model, status, businessKey);
        };
        return;
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    int x = 2;
    NSString *str = [NSString stringWithFormat:@"fsfsfsdfsfsdf"];

    [self addPKAnimationUpdateBlock:^(NSObject *statusObj, NSObject *model, int status, NSString *businessKey) {

        NSLog(@"x = %d, str = %@", x, str);

    } forBusiness:str];
}

  经过代码验证,stackBlock作为参数传递的时候,需要确保对其进行copy操作,否则stackBlock在函数返回之后会被释放,造成野指针。

 上面的方法执行完毕之后,传入的block参数作为 stackBlock 类型依然没有发生改变;对其进行拷贝之后变成了mallocBlock。

二、问题总结分析

  1)block分类

    1、_NSConcreteGlobalBlock全局的静态 block,不会访问任何外部变量;
    2、_NSConcreteStackBlock保存在栈中的 block,当函数返回时会被销毁;
    3、_NSConcreteMallocBlock保存在堆中的 block,当引用计数为0时会被销毁;

  2)block对外部变量的引用

    一、静态变量 和 全局变量   在加和不加  __block 都会直接引用变量地址。也就意味着 可以修改变量的值。在没有加__block 参数的情况下。
      全局block 和 栈block 区别为 是否引用了外部变量,堆block 则是对栈block  copy 得来。对全局block copy 不会有任何作用,返回的依然是全局block。
    二, 常量变量(NSString *a = @"hello";a 为常量变量,@“hello”为常量。)-----不加__block类型 block 会引用常量的地址(浅拷贝)。加__block类型 block会去引用常量变量(如:a变量,a = @"abc".可以任意修改a 指向的内容。)的地址。 
    三、对象变量 如(MyClass *class、Block block)。 这里block 也是”类“对象(类似对象,其包含isa指针,clang 反编译可以查看。因为它不像从NSObject 继承下来的对象都支持 retain、copy、release)。
      Block的copy、retain、release操作不同于NSObjec的copy、retain、release操作:
      对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;
      NSGlobalBlock:retain、copy、release操作都无效;
      NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry addObject:stackBlock],在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。
      NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;
      尽量不要对Block使用retain操作。

  

  3)block作为外部变量的时候,确保其被copy的场景

    在ARC环境,大多数情况下编译器会适当地进行判断,会自动生成将Block从栈上复制到堆上的代码。    将Block作为函数返回值返回时,编译器会自动生成复制到堆上的代码。    编译器不能判断“自动将Block从栈上复制到堆上”的情况:向方法或函数的参数传递Block

  比如下面的代码:

  

-(id)getBlockArray
{
    int val = 10;
    //Block变量类型可以直接调用copy方法。所以说Block其实也是Objective-C对象。
    //不管Block配置在堆、栈或者数据区域,用copy方法复制都不会引起任何问题。
    return [[NSArray alloc] initWithObjects:[^{NSLog(@"blk0:%@",@(val));} copy],[^{NSLog(@"blk1:%@",@(val));} copy], nil];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    //正常执行。
    id obj = [self getBlockArray];
    blk_t blk = (blk_t)[obj objectAtIndex:0];
    blk();
}

  4)block什么时候会被copy

1.  调用Block的copy实例方法时;
2.  Block作为函数返回值返回时;
3.  将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时;
4.  在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中传递Block时。

  

三、引用资料

 1) ARC环境下Block的内存管理 

    作者:sxtra

    链接:https://www.jianshu.com/p/0fad960d6795

 2)https://www.cnblogs.com/DamonTang/p/4146728.html

原文地址:https://www.cnblogs.com/doudouyoutang/p/9588452.html

时间: 2024-10-08 10:09:22

ARC下面的Block对内存的管理方式的相关文章

ARC下需要注意的内存管理

ARC下需要注意的内存管理 2016/04/03 · iOS开发 · 内存管理 分享到:1 原文出处: 一不(@luoyibu) 之前发了一篇关于图片加载优化的文章,还是引起很多人关注的,不过也有好多人反馈看不太懂,这次谈谈iOS中ARC的一些使用注意事项,相信做iOS开发的不会对ARC陌生啦.这里不是谈ARC的使用,只是介绍下ARC下仍然可能发生的内存泄露问题,可能不全,欢迎大家补充. Ps:关于ARC的使用以及内存管理问题,强烈建议看看官方文档,里面对内存管理的原理有很详细的介绍,相信用过M

iOS: ARC和非ARC下使用Block属性的问题

1. Block的声明和线程安全 Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的,可以参考之前的文章(iOS: 非ARC下返回Block). 另一个需要注意的问题是关于线程安全,在声明Block属性时需要确认“在调用Block时另一个线程有没有可能去修改Block?”这个问题,如果确定不会有这种情况发生的话,那么Block属性声明可以用nonatomic.如果不肯定的话(通常情况是这样的),那么你首先需要声明Blo

linux 下的 service 和systemctl 服务管理方式

linux 下的 service  和systemctl 服务管理方式 man service 得到:执行一个system V 风格的启动脚本 service - run a System V init script man systemctl 得到: 控制systemd 系统和服务管理器 systemctl - Control the systemd system and service manager systemd:完全替代init,可并行启动服务,并能减少在shell上的系统开销,相比传

ARC下需要注意的内存问题

之前发了一篇关于图片加载优化的文章,还是引起很多人关注的,不过也有好多人反馈看不太懂,这次谈谈iOS中ARC的一些使用注意事项,相信做iOS开发的不会对ARC陌生啦.这里不是谈ARC的使用,只是介绍下ARC下仍然可能发生的内存泄露问题,可能不全,欢迎大家补充. Ps:关于ARC的使用以及内存管理问题,强烈建议看看官方文档,里面对内存管理的原理有很详细的介绍,相信用过MRC的一定看过这个. 另也有简单实用的ARC使用教程:ARC Best Practices 在2011年的WWDC中,苹果提到90

内存的管理方式

1.内存的区域     对于内存的区域划分上,不同的区域划分上都各有不同. 划分1: 代码区.堆.栈. 全局区(静态存储区). 文字常量区. 划分2: 代码段.堆.栈.  data段.BSS段.文字常量区 全局区:     又成为静态存存储区.保存的是全局变量和静态变量(带有static 关键字).全局区分为两个区域:一个区域保存的是经过初始化,且初始化的值不为零的全部变量和静态变量:一个区域保存的是没有经过初始化或者初始化的值为零的.程序结束由 OS 进行释放 常量区:     将一些不可以被

__block在MRC ARC下的区别

研究下__block在MRC/ARC下区别,直接上代码. @property (nonatomic,copy) TestBlock   block;//定义的block 一._NSConcreteStackBlock - (void)stackBlock{ NSLog(@"stackBlock start...."); Person *person = [[Person alloc]init];person.personName = @"张三"; NSLog(@&q

__block在arc和非arc下含义一样吗?

Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的,可以参考之前的文章(iOS: 非ARC下返回Block). 比如这样一个Block类型: typedef void (^MyBlockType)(int); @property (copy) MyBlockType myBlock; if (self.myBlock) { //此时,走到这里,self.myBlock可能被另一个线程改为空,造成crash //注意:a

内存连续分配管理方式

内存连续分配方式,是指为一个用户程序分配一个连续的内存空间.它主要包括单一连续分配.固定分区分配和动态分区分配. 单一连续分配 内存在此方式下分为系统区和用户区,系统区仅提供给操作系统使用,通常在低地址部分:用户区是为用户提供的.除系统区之外的内存空间.这种方式无需进行内存保护. 这种方式的优点是简单.无外部碎片,不需要额外的技术支持.缺点是只能用于单用户.单任务的操作系统中,有内部碎片,存储器的利用率极低. 固定分区分配 固定分区分配是最简单的一种多道程序存储管理方式,它将用户内存空间划分为若

八.OC基础加强--1.autorelease的用法 2.ARC下内存管理 3.分类(category)4.block的学习

1.autorelease的用法   1.自动释放池及autorelease介绍 (1)在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的. (2)当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中 . 2.为什么会有autorelease? OC的内存管理机制中比较重要的一条规律是:谁申请,谁释放. 但有些情况下,开发者并不能确定某些对象何时释放.这时候就需要自动释放池. 它的好处是: (1)不需要再关心对象释放的时间 : (2)不需要再关