从Signal 10 Bus Error聊聊assign和weak

曾经我以为 iOS 最牛叉的错误是 EXC_BAD_ACCESS,直到我遇到了 Signal 10 was raised. SIGBUS ……

这个错误出现的几率其实蛮低的,但是会造成闪退,所以还是不容忽视的。下面我们就来一起看一下

Bus error

Bus error 即总线错误,以下是中文维基百科的定义

存储器区块错误(英语:Segmentation fault,经常被缩写为 segfault),又译为记忆段错误,也称为总线错误(bus error),或总线错误、访问权限冲突(access violation),是一种程序错误。

它会出现在当程序企图访问 CPU 无法定址的存储器区块时。当错误发生时,硬件会通知操作系统,产生了存储器访问权限冲突的状况。

英文维基百科的释义相对详细一些,也介绍了造成这种错误的原因

On POSIX-compliant platforms, bus errors usually result in the SIGBUS signal being sent to the process that caused the error. SIGBUS can also be caused by any general device fault that the computer detects, though a bus error rarely means that the computer hardware is physically broken—it is normally caused by a bug in a program’s source code.Bus errors may also be raised for certain other paging errors; see below.

There are at least three main causes of bus errors:

  • Non-existent address
  • Unaligned access
  • Paging errors

上面这段话的大致意思是说在类 POSIX 平台,Bus errors 通常是因为 SIGBUS 信号被发送到当前线程导致错误。SIGBUS 也可以由一些硬件设备错误导致,但是这个几率极低,因此一般都是我们编码错误导致的。Bus errors 也可以由特定的分页错误导致。下面是三种主要的导致 Bus errors 的原因,即

  • Non-existent address(访问不存在的内存地址)
  • Unaligned access(访问未对齐的内存地址)
  • Paging errors(分页错误)

看了上面的解释,我们就意识到这是一个内存访问错误了。

错误解决

因为这个错误的出现几率不是很高,debug 了挺长时间也没有进展,直到我看到了某个 delegate 的修饰符是 assign。作为 mrc 时代走过来的 iOS 开发者刚开始对此感觉并没有什么异样,但是后来发现问题就出在这里,需要将 delegate 的修饰符修改为 weak。

1
2
3
4
5
//不安全的写法
@property (assign, nonatomic) id<VinScanDelegate> delegate;

//安全的写法
@property (weak, nonatomic) id<VinScanDelegate> delegate;

但是为什么呢?我们来看下 assgin 和 weak 的区别

assgin&weak

在 arc 中,assign 和 weak 非常类似,但是他们还是有一些细微的区别的

weak 会在所指向的对象释放消失之后自动置为 nil,这样就会避免野指针导致的 crash,也就不会导致上面所提到的内存错误了

“The main difference between weak and assign is that the with weak, once the object being pointed to is no longer valid, the pointer is nilled out. Assigning the pointer the value nil avoids many crashes as messages sent to nil are essentially no-ops”

有人写了一个小 demo 来验证这个问题,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#import "ViewController.h"

@interface ViewController ()
@property (nonatomic,weak) id weakPoint;
@property (nonatomic,assign) id assignPoint;
@property (nonatomic,strong) id strongPoint;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.strongPoint = [NSDate date];
NSLog(@"strong 属性:%@",self.strongPoint);
self.weakPoint = self.strongPoint;
self.assignPoint = self.strongPoint;
self.strongPoint = nil;
NSLog(@"weak 属性:%@",self.weakPoint);
//NSLog(@"assign 属性:%@",self.assignPoint);
}
@end

//当程序中的注释被打开时,运行程序有可能会崩溃(有时候不崩溃,你可能需要多运行几次)
//这是因为当 assign 指针所指向的内存被释放(释放并不等于抹除,只是引用计数为 0),不会自动赋值 nil ,
//这样再引用 self.assignPoint 就会导致野指针操作
//如果这个操作发生时内存还没有改变内容,依旧可以输出正确的结果
//而如果发生时内存内容被改变了,就会 crash。

所以,在 arc 情况下,指针类型最好使用 strong 或者 weak 来修饰,基本类型和结构体用 assign,委托要用 weak 来修饰。