iOS开发之避免crash

这篇文章列出了9种常见的crash,原文写得很好,我这里对照我本身遇到过的状况再整理记录下。javascript

(一)KVO

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

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

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

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

People.h编程

#import <Foundation/Foundation.h>

@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后没有移除监听。async

移除监听的方式

咱们能够根据具体的通知名称移除,例如性能

[[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跟踪记录下来
  • 代码写出来应该是看起来很清晰的,若是看起来很绕,那么是须要重构了

参考资料:inessential.com/hownottocra…


最后作个推广,欢迎关注公众号 MrPeakTech,我从这里学到不少,推荐给你们,共同进步~

相关文章
相关标签/搜索