不要濫用懶載入

wxiubin發表於2017-07-20

寫程式碼是一種習慣的養成,一種生活的態度。

有一次同事看著我寫的程式碼說,你為什麼要這麼寫啊?

我看了一下,原來是在 ViewController 和 Cell 裡初始化檢視,還有資料模型陣列的時候,我都是用的懶載入(Lazy-Load)。

是啊,我為什麼這麼喜歡用懶載入來例項化一個屬性呢?

以前學 iOS 開發的時候,

  1. 覺得懶載入可以延遲載入,需要的時候才去載入資料;

  2. 陣列和字典等集合型別還可以防止為初始化或者使用中被置為 nil

  3. 類的屬性多了這麼寫看著更舒服、清晰
    。。。

基本上每個屬性我都希望去懶載入實現它,這會給我一種錯覺:這樣寫更好,效能更高!

其實,這是一個不好的習慣,隨著程式設計時間越發的長,越是覺得之前有些偏激。

iOS 中懶載入的寫法一般為重寫 getter 方法,判斷屬性是否為 nil ,是的話去初始化,否就直接返回:

- (UIView *)layerView {
    if (!_layerView) {
        _layerView = [UIView new];
    }
    return _layerView;
}

但是我們有更清晰、簡潔的寫法

一般來說,如果 layerView 是控制器的屬性,我們一般都會在 viewDidLoad 方法中去載入檢視;如果是一個檢視,我們一般會在 initWithFrame: 載入子檢視,我們只需要安安靜靜的用以下程式碼來初始化即可:

 _layerView = [UIView new];

根本無需使用懶載入,因為如果你不是一個人在開發的話,你永遠不會知道你的隊友會在 get 方法裡面做什麼

而且這樣寫更簡潔,更清晰。當屬性很多的時候也可以使用以下方式來初始化:

self.layerView = ({
    [UIView new];
});

用懶載入至少六行程式碼,現在只需要一行或者三行就可以做到。

我們不能使用懶載入來防止那些可能出現的錯誤

很大一部分人用懶載入是為了保證陣列和字典等集合型別在使用中永遠不會是空值,這是錯誤的做法,因為可變集合型別被初始化之後,在正確的使用中如果不會被置 nil,那麼也無需使用懶載入。如果因此而引發的問題,也可以幫我們提前找到原因。

對於耗時或效能很大的操作,我們可以使用惰性計算而不是懶載入

比如,我重構專案遇到的一個需求:請求股票列表返回的資料會告訴我總共會有上千條資料,並且不做分頁,就是全部展示,滑到第幾條就去請求第幾條的資料。

上千條資料不做分頁,我們也不可能全部請求回來,即便能全部請求回來也不可能在一個方法裡去做這樣的操作:

NSMutableArray *dataArray = [NSMutableArray array];
for (int i = 0; i < 100000; ++i) {
  [dataArray addObject:data];
}

但是,對於 TableView 來講,上千條資料,當然需要 Array 的 count 返回是一千。這個時候我們可以用惰性計算來解決這個問題:有多少條資料,我們就讓陣列返回的 count 是多少,但是隻有真正的向陣列取這個下標的物件的時候,我們才去處理!

那我們繼承 NSArray 來寫(真正的寫一個 NSArray 還需要重寫其他幾個方法,在此不細說):

typedef id(^HTLazyArrayItemBlock)(NSInteger index);
@interface HTLazyArray : NSArray
- (instancetype)initWithItemBlock:(HTLazyArrayItemBlock)block count:(NSInteger)count;
@end

#import "HTLazyArray.h"
@interface HTLazyArray()
@property (nonatomic, copy) HTLazyArrayItemBlock block;
@end

@implementation HTLazyArray {
    NSInteger _ct;
}
- (instancetype)initWithItemBlock:(HTLazyArrayItemBlock)block count:(NSInteger)count {
    if (self = [super init]) {
        _ct = count;
        self.block = block;
    }
    return self;
}
#pragma mark - override
- (NSUInteger)count {
    return _ct;
}
- (id)objectAtIndex:(NSUInteger)index {
    return self.block(index);
}
@end

我們初始化的時候傳入一個 count,被 TableView 的代理方法訪問的時候,有則返回資料模型,沒有就先返回 nil,待到網路請求到資料再進行重新整理。這樣做的效能損耗微乎其微。

self.lazyArray = [[HTLazyArray alloc]initWithItemBlock:^id(NSInteger index) {
       HTQuoteAHCellModel *model = weakSefl.cache[@(index)];
       return model;
   } count:dataTotalCount];

那麼,我們到底什麼時候該用懶載入呢?

懶載入的使用需要看具體的場景,比如一個很可能不會被使用的屬性,使用懶載入確實可以避免無所謂的效能損耗;
還有就是 null_resettable 修飾的屬性,該屬性意為:setter nullable,但是 getter nonnull,典型的就是控制器的 view 屬性:“你可以不要我,把我置空;但只要你需要我,我就是在的”。諸如此類都可以使用懶載入。

首發於https://iosgg.cn/2017/02/18/dont_abuse_lazy_load/

相關文章