从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。

//不安全的写法
@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来验证这个问题,如下:

#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来修饰。