zoukankan      html  css  js  c++  java
  • block专递参数导致野指针引发crash

    一、问题引入

      近日开发中引入一个随机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时。
    

     

    三、关于block被嵌套的问题

      看下面的代码:

      

       在运行到56行的时候,str的引用计数是多少呢?

      

     

       在运行到78行的时候,str的引用计数是多少呢?

       在78行的时候,为什么是4

      首先创建的时候是1,54行str指针引用加1 = 2; 56行第一次执行第一层block引用str加1 ; 第一层中的指针str引用加1 总共等于4

      这也是嵌套block的时候内存管理的方式,嵌套的block执行的时候,运行时只能处理第一层block中引用的外部变量

      此处以后要多加注意。

    四、引用资料

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

        作者:sxtra

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

     
     2)https://www.cnblogs.com/DamonTang/p/4146728.html
  • 相关阅读:
    如何心无旁鹜的编程
    [转]虚拟现实和现实增强技术带来的威胁
    Mac上好用软件集锦
    无论如何都要来报到
    Unity3D脚本语言UnityScript初探
    X3DOM新增剪裁平面节点ClipPlane支持
    用Perl编写Apache模块续二
    如何浪费自己青春
    macbook 我们需要买吗
    看了一本Unity3D的教程
  • 原文地址:https://www.cnblogs.com/doudouyoutang/p/9588452.html
Copyright © 2011-2022 走看看