iOS 開發之避免 crash

Jonkamkw 8年前發布 | 7K 次閱讀 iOS開發 移動開發

這篇文章 列出了幾種常見的 crash,原文寫得很好,我這里對照我自己遇到過的情況再整理記錄下。

(一)KVO

KVO 的一種常用場景是 view 對象監聽 view model 對象實現實時刷新 UI,例如有一個 table view,每個 cell 都監聽對應的 cell model,這樣數據源數組中只有一個對象的屬性發生改變時就不需要 reload 整個列表。

使用 KVO 有一個常見的 crash 就是沒有移除監聽,我們需要在 dealloc 方法中執行 removeObserver 方法。這里推薦 非死book 開源的 KVOController ,讓我們更方便地使用 KVO。

(二)遍歷可變集合時對集合做修改

我們經常會遇到集合遍歷的 crash,有一點需要注意,在遍歷可變集合(NSMutableArray,NSMutableDictionary,NSMutableSet)時,不能夠對集合做修改,例如增加或刪除集合中的元素。這個問題最好是從代碼規范上避免,例如接口中不應該暴露可變集合,而是暴露 readonly 的集合。以下是推薦的一種寫法:

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 后沒有移除監聽。

移除監聽的方式

我們可以根據具體的通知名稱移除,例如

[[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://www.jianshu.com/p/dce3c87d3ca1

 

 本文由用戶 Jonkamkw 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!