iOS 开发之避免 crash

Jonkamkw 7年前
   <p>这篇文章 列出了几种常见的 crash,原文写得很好,我这里对照我自己遇到过的情况再整理记录下。</p>    <h3><strong>(一)KVO</strong></h3>    <p>KVO 的一种常用场景是 view 对象监听 view model 对象实现实时刷新 UI,例如有一个 table view,每个 cell 都监听对应的 cell model,这样数据源数组中只有一个对象的属性发生改变时就不需要 reload 整个列表。</p>    <p>使用 KVO 有一个常见的 crash 就是没有移除监听,我们需要在 dealloc 方法中执行 removeObserver 方法。这里推荐 非死book 开源的 KVOController ,让我们更方便地使用 KVO。</p>    <h3><strong>(二)遍历可变集合时对集合做修改</strong></h3>    <p>我们经常会遇到集合遍历的 crash,有一点需要注意,在遍历可变集合(NSMutableArray,NSMutableDictionary,NSMutableSet)时,不能够对集合做修改,例如增加或删除集合中的元素。这个问题最好是从代码规范上避免,例如接口中不应该暴露可变集合,而是暴露 readonly 的集合。以下是推荐的一种写法:</p>    <p>People.h</p>    <pre>  <code class="language-objectivec">#import <Foundation/Foundation.h>    @interface People : NSObject    @property (nonatomic, strong, readonly) NSArray *friends;    - (void)addFriend:(id)aFriend;  - (void)removeFriend:(id)aFriend;    @end</code></pre>    <p>People.m</p>    <pre>  <code class="language-objectivec">#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</code></pre>    <p>还有一点要注意的是,对于第三方接口返回的集合,我们都要怀疑其正确性,有可能接口中写明是不可变的但是实际返回的是可变集合,如果我们直接按照不可变来使用就有可能触发 crash,因此在集合遍历前先对第三方接口返回的数据做一次 copy 操作是一个好的习惯。</p>    <h3><strong>(三)NSNotification</strong></h3>    <p>NSNotification 是一种一对多的监听机制,有一种常见的 crash 是对象 dealloc 后没有移除监听。</p>    <p>移除监听的方式</p>    <p>我们可以根据具体的通知名称移除,例如</p>    <pre>  <code class="language-objectivec">[[NSNotificationCenter defaultCenter] removeObserver:self name:kSomeNotificationName object:someObject];  [[NSNotificationCenter defaultCenter] removeObserver:self name:kSomeOtherNotificationName object:someOtherObject];  etc...</code></pre>    <p>上述方法没有问题,但是不利于维护,比如后期又有需求需要添加新的通知来实现,对应的就需要添加代码来移除,要是一不小心忘记移除就会触发 crash,更加推荐的方式是在 dealloc 中使用</p>    <p>[[NSNotificationCenter defaultCenter] removeObserver:self]; 来移除</p>    <p>重复监听</p>    <p>在注册监听通知时有一个问题需要注意,经测试,重复注册会导致回调方法进入多次,注册几次,回调就会进入几次。我们经常在 viewDidLoad 中注册监听,但是view是有可能 unloaded 再 reloaded 的,因此 viewDidLoad 就有可能执行多次导致重复注册。</p>    <p>在init方法中注册,在dealloc方法中移除</p>    <p>对于一个对象,它的 init 方法只会执行一次,dealloc 方法也是,因此在这两个方法中执行注册和移除就能保证注册和移除是平衡的,降低了问题排查的难度。</p>    <p>避免使用addObserverForName</p>    <p>[NSNotificationCenter addObserverForName:​object:​queue:​usingBlock:] 提供了 block 的方法来使用通知,但是我们应该避免使用这种方式,因为这需要我们在后续代码里单独移除,这就增加了出错的可能,不像上述提到的能在 dealloc 统一移除。</p>    <h3><strong>(四)处理空的情况</strong></h3>    <p>我们知道,在 Objective-C 中,对 nil 发送消息是没有问题的,例如</p>    <p>[thing doStuff];</p>    <p>这种写法没有问题,但是如果参数是 nil,则取决于具体的方法是如何实现的,例如:</p>    <p>[self doStuff:thing];</p>    <p>这种情况就要看 thing 是拿来做什么,如果方法实现里有如下代码</p>    <p>menuItem.title = thing;</p>    <p>menuItem 是 NSMenuItem,那么当 thing 为空时就会导致 crash。</p>    <p>一种推荐的做法是使用断言对参数做空的判断,具体如下:</p>    <pre>  <code class="language-objectivec">- (void)someMethod:(id)someParameter {    NSParameterAssert(someParameter);    …do whatever…  }</code></pre>    <h3><strong>(五)越界</strong></h3>    <p>常见的越界 crash 就是数组越界,当然还有其他的越界,比如 NSrange,对于这些的使用,推荐的做法是在使用前都做一下范围校验,这也是需要注意的点。</p>    <h3><strong>(六)非主线程处理UI事件</strong></h3>    <p>在非主线程处理UI事件会导致不可预知的事情发生,有可能 crash,有可能是 UI 显示异常。比如我们在子线程执行了一段耗时的计算任务,然后将计算结果传递给 UI 去更新显示,这时候我们需要</p>    <pre>  <code class="language-objectivec">dispatch_async(dispatch_get_main_queue(), ^{        });</code></pre>    <p>另外, 原文 作者还提出了一些他的编程实践经验,例如:</p>    <ul>     <li>应尽可能的将任务放到主线程排队执行,这样能避免大多数多线程问题,除非是经检测有性能瓶颈的任务需要放到子线程,并且他也是偏向于将独立的任务放到子线程中</li>     <li>尽可能使用点语法(_property = xxx的方式赋值不会触发KVO)、ARC、weak属性</li>     <li>建立完善的 crash 收集机制,并且将 bug 跟踪记录下来</li>     <li>代码写出来应该是看起来很清晰的,如果看起来很绕,那么是需要重构了</li>    </ul>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/dce3c87d3ca1</p>    <p> </p>