一、问题引入

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

block专递参数导致野指针引发crash的更多相关文章

  1. 使用AFNetworking时, 控制器点击返回销毁了, 但还是会执行请求成功或失败的block, 导致野指针异常

    原本我以为是我程序框架有问题...后来才知道, 无知真可怕... __unsafe_unretained __block typeof(self) weakSelf = self; AFHTTPSes ...

  2. iOS objc_msgSend 野指针Crash 从 Log 提取 Crash 时 selector 的地址和名字并打印

    从 crash stack log 里面,提取 objc_msgSend 关键字,定位是否是野指针问题导致的crash,如果是则打印 crash 时的 objc_msgSend 调用的第二个参数,即 ...

  3. 七.OC基础加强--1.内存管理 2.野指针,内存泄露 3.set方法的内存管理 4.@property参数 5.@class和循环retain的使用 6.NSString的内存管理

    1,内存管理简单介绍 1,为什么要有内存管理? malloc selloc dealloc```需要回头复习 一般的内存 4s 是512m内存:6 是1024m内存: 当内存过大时,会耗尽内存.出现程 ...

  4. MRC下delegate 野指针问题

    最近项目开发中,临时被调去修复一个页面返回时crash的问题.出现这个问题的原因也很巧合,正好服务地址在同事电脑上,也正巧网络请求响应时间狂慢!一个请求发出去回来的时间是40秒左右,要是在线上,肯定会 ...

  5. linux kernel elv_queue_empty野指针访问内核故障定位与解决

    1. 故障描述 故障操作步骤: 单板上插了一个U盘,出问题前正在通过FTP往单板上拷贝文件,拷贝的过程中单板自动重启. 故障现象: Entering kdb (current=0xc000000594 ...

  6. C中的野指针—如何避免

    转自:http://www.cnblogs.com/viviwind/archive/2012/08/14/2638810.html 先看一个例子: struct student{ char* nam ...

  7. C语言之free函数及野指针

    [FROM MSDN && 百科] 原型:  void free(void *ptr); #include<stdlib.h>或#include <malloc.h& ...

  8. 关于空指针NULL、野指针、通用指针 (转)

    reference:https://www.cnblogs.com/losesea/archive/2012/11/16/2772590.html 首先说一下什么是指针,只要明白了指针的含义,你就明白 ...

  9. Package manager has died异常PackageInfo 引发 Crash

    Android 获取 PackageInfo 引发 Crash 填坑 一般 Android 通过PackageInfo这个类来获取应用安装包信息,比如应用内包含的所有Activity名称.应用版本号之 ...

  10. 奇思妙想:利用野指针和lower_bound()/upper_bound()函数实现整数二分

    众所周知,c++的STL中提供了三个二分查找函数,binary_search(),lower_bound(),upper_bound(),功能分别是找某值是否在数组中出现,找到数组中第一个大于等于某值 ...

随机推荐

  1. json文件读取并转换成为字典python

    # JSON到字典转化 f2 = open('info.json', 'r') info_data = json.load(f2) print(info_data) # 显示数据类型 print(ty ...

  2. 聊聊从大模型来看NLP解决方案之UIE

    转载请备注出处:https://www.cnblogs.com/zhiyong-ITNote 概述 自然语言处理NLP任务的实现,相比较以前基于传统机器学习算法实现方法,现在越来越集中使用大模型来实现 ...

  3. Java实现控制台购书系统

    "感谢您阅读本篇博客!如果您觉得本文对您有所帮助或启发,请不吝点赞和分享给更多的朋友.您的支持是我持续创作的动力,也欢迎留言交流,让我们一起探讨技术,共同成长!谢谢!" 代码 im ...

  4. Dragonfly 基于 P2P 的文件和镜像分发系统

    简介: 业界软件生态在优化 HTTPS 的性能上也做了诸多探索,传统的软件优化方案在软件层面的优化无法满足流量日益增长的速度,CPU 硬件加速成为业界一个通用的解决方案. 作者:孙景文.吴迪   背景 ...

  5. 阿里云 Serverless 助力企业全面拥抱云原生

    ​简介:相信随着云计算的发展,Serverless 将成为云时代默认的计算范式,越来越多的企业客户将会采用这个技术. 作者:洛浩 Serverless 应用引擎的组件架构 最早的时候,大家设计软件一般 ...

  6. [GPT] jquery chosen插件选择的多个元素是逗号分隔的,怎么设置成其它分隔符号 ?

    如果你想要在 jQuery Chosen 插件中使用其它分隔符号,可以通过以下方式实现: 1. 设置 delimiter 选项为一个包含所需分隔符的字符串. $(".chosen-selec ...

  7. [Ethereum] Gas Station Network (GSN) eip-1613 与 Gas Relay Network (GRN) eip-1077

    在 Ethereum dapp 中,任何涉及状态改动的交易都需要消耗 Gas,这限制了很多没有钱包或者 ETH 的用户对 dapp 的采用. 理念 让非以太用户能够访问智能合约 (如dapps),允许 ...

  8. win10 uwp 选择文本转语音的机器人

    在 UWP 里,可以非常方便将某个文本转换为音频语音,转换时,将会根据输入的内容以及本机所安装的语言库选择一位机器人帮忙将输入的文本转换为语音.本文来告诉大家如何切换文本转语音的机器人,例如从默认的女 ...

  9. dotnet 教你写一个可以搞炸本机所有 WCF 应用的程序方法

    作为团队里面挖掘机出身的我,怎么能不多挖一些坑好将小伙伴们都埋进去呢.本文来告诉大家一个有趣且简单的方法,此方法可以将本机的 WCF 玩坏,不敢说真的搞炸本机所有 WCF 应用,但搞炸大部分基于 WC ...

  10. 火山引擎ByteHouse:OLAP如何支持超高QPS点查?

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 在当今高速发展的互联网时代,信息传播迅速,用户数量激增.在面对如此庞大的用户群体和高频的访问需求时,系统高并发访问 ...