博客> ios开发之避免crash
ios开发之避免crash
2018-11-17 05:02 评论:0 阅读:770 Vip密斯特韦
ios crash

(一)KVO

KVO的一种常用场景是view对象监听view model对象实现实时刷新UI,例如有一个table view,每个cell都监听对应的cell model,这样数据源数组中只有一个对象的属性发生改变时就不需要reload整个列表。

使用KVO有一个常见的crash就是没有移除监听,我们需要在dealloc方法中执行removeObserver方法。这里推荐facebook开源的KVOController,让我们更方便地使用KVO。

(二)遍历可变集合时对集合做修改

我们经常会遇到集合遍历的crash,有一点需要注意,在遍历可变集合(NSMutableArray,NSMutableDictionary,NSMutableSet)时,不能够对集合做修改,例如增加或删除集合中的元素。这个问题最好是从代码规范上避免,例如接口中不应该暴露可变集合,而是暴露readonly的集合。以下是推荐的一种写法:

`People.h

import

@interface People : NSObject @property (nonatomic, strong, readonly) NSArray *friends;

  • (void)addFriend:(id)aFriend;
  • (void)removeFriend:(id)aFriend; @end`

People.m

import "People.h"

@interface People () @property (nonatomic, strong) NSMutableArray *internalFriends; @end @implementation People

  • (void)dealloc { // }
  • (instancetype)init { self = [super init]; if (self) { _internalFriends = [NSMutableArray new]; } return self; }
  • (void)addFriend:(id)aFriend { if (aFriend == nil) { return; } @synchronized(self) { [_internalFriends addObject:aFriend]; } }
  • (void)removeFriend:(id)aFriend { if (aFriend == nil) { return; } @synchronized(self) { [_internalFriends removeObject:aFriend]; } } //NSMutableArray copy -> NSArray
  • (NSArray *)friends { return [_internalFriends copy]; } @end

还有一点要注意的是,对于第三方接口返回的集合,我们都要怀疑其正确性,有可能接口中写明是不可变的但是实际返回的是可变集合,如果我们直接按照不可变来使用就有可能触发crash,因此在集合遍历前先对第三方接口返回的数据做一次copy操作是一个好的习惯。

(三)NSNotification

NSNotification是一种一对多的监听机制,有一种常见的crash是对象dealloc后没有移除监听。

移除监听的方式

我们可以根据具体的通知名称移除,例如

[[NSNotificationCenter defaultCenter] removeObserver:self name:kSomeNotificationName object:someObject]; [[NSNotificationCenter defaultCenter] removeObserver:self name:kSomeOtherNotificationName object:someOtherObject]; etc...

上述方法没有问题,但是不利于维护,比如后期又有需求需要添加新的通知来实现,对应的就需要添加代码来移除,要是一不小心忘记移除就会触发crash,更加推荐的方式是在dealloc中使用 [[NSNotificationCenter defaultCenter] removeObserver:self];来移除

重复监听

在注册监听通知时有一个问题需要注意,经测试,重复注册会导致回调方法进入多次,注册几次,回调就会进入几次。我们经常在viewDidLoad中注册监听,但是view是有可能unloaded再reloaded的,因此viewDidLoad就有可能执行多次导致重复注册。

在init方法中注册,在dealloc方法中移除

对于一个对象,它的init方法只会执行一次,dealloc方法也是,因此在这两个方法中执行注册和移除就能保证注册和移除是平衡的,降低了问题排查的难度。

避免使用addObserverForName

[NSNotificationCenter addObserverForName:​object:​queue:​usingBlock:] 提供了block的方法来使用通知,但是我们应该避免使用这种方式,因为这需要我们在后续代码里单独移除,这就增加了出错的可能,不像上述提到的能在dealloc统一移除。

(四)处理空的情况

我们知道,在Objective-C中,对nil发送消息是没有问题的,例如

[thing doStuff];

这种写法没有问题,但是如果参数是nil,则取决于具体的方法是如何实现的,例如:

[self doStuff:thing];

这种情况就要看thing是拿来做什么,如果方法实现里有如下代码

menuItem.title = thing;

menuItem是NSMenuItem,那么当thing为空时就会导致crash。

一种推荐的做法是使用断言对参数做空的判断,具体如下:

  • (void)someMethod:(id)someParameter { NSParameterAssert(someParameter); …do whatever… }

五)越界

常见的越界crash就是数组越界,当然还有其他的越界,比如NSrange,对于这些的使用,推荐的做法是在使用前都做一下范围校验,这也是需要注意的点。

(六)非主线程处理UI事件

在非主线程处理UI事件会导致不可预知的事情发生,有可能crash,有可能是UI显示异常。比如我们在子线程执行了一段耗时的计算任务,然后将计算结果传递给UI去更新显示,这时候我们需要

dispatch_async(dispatch_get_main_queue(), ^{

});

另外,原文作者还提出了一些他的编程实践经验,例如:

应尽可能的将任务放到主线程排队执行,这样能避免大多数多线程问题,除非是经检测有性能瓶颈的任务需要放到子线程,并且他也是偏向于将独立的任务放到子线程中 尽可能使用点语法(_property = xxx的方式赋值不会触发KVO)、ARC、weak属性 建立完善的crash收集机制,并且将bug跟踪记录下来 代码写出来应该是看起来很清晰的,如果看起来很绕,那么是需要重构了 参考资料:http://inessential.com/hownottocrash

收藏
0
sina weixin mail 回到顶部