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,我從這裏學到不少,推薦給你們,共同進步~

相關文章
相關標籤/搜索