【Bug記錄】懶載入的坑
一、問題
最近開發過程中,遇到了一個詭異的bug:
vc下有個collectionView屬性,並通過懶載入方式獲取:
- (UICollectionView *)collectionView {
if (!_collectionView) {
_collectionView =
[[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:self.collectionViewLayout];
_collectionView.dataSource = self ;
_collectionView.delegate = self;
}
return _collectionView;
}
在頁面viewDidLoad中通過[self.view addSubview:self.collectionView];
加入,並設定好dataSource和delegate。在請求資料後reloadData
,頁面沒有任何更新
二、原因
在初步的debug中,發現在執行reloadData時,collectionView.superview 為 nil
而我們明明在viewDidLoad 中已經addSubview了
定位原因:
問題出在VC中有個對外暴露的方法:
- (void)setScrollsToTop:(BOOL)scrollsToTop {
self.collectionView.scrollsToTop = scrollsToTop;
}
在外部init了VC後,便呼叫了此方法。
當呼叫此方法時,viewDidLoad是還沒開始呼叫的。
在self.collectionView呼叫其get方法時,get方法內部又使用了self.view
[[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:self.collectionViewLayout ];
由於self.view的呼叫,提前觸發了viewDidLoad方法中的
[self.view addSubview:self.collectionView];
而self.collectionView又會執行collectionView的get方法,而此時,_collectionView還為nil(第一次執行的[collectionView init]
還未返回),因此get會再新建一個新例項,最終返回add到self.view中
接著,堆疊返回上一個呼叫棧(即是第一次呼叫get方法中的[collectionView init]
),返回collectionView例項,但此時_collectionView已經有了(被addSubview了),結果就是把_collectionView重新覆蓋掉了
三 、 程式碼例子
@interface ViewController : UIViewController
@property (strong, nonatomic) UICollectionView *collectionView;
- (void)setScrollsToTop:(BOOL)scrollsToTop;
@end
@implementation
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.collectionView];
}
- (void)setScrollsToTop:(BOOL)scrollsToTop {
self.collectionView.scrollsToTop = scrollsToTop;
}
- (UICollectionView *)collectionView {
if (!_collectionView) {
_collectionView =
[[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:self.collectionViewLayout];
_collectionView.dataSource = self;
_collectionView.delegate = self;
}
return _collectionView;
}
@end
外部呼叫如下:
ViewController *vc = [[ViewController alloc] init];
[vc setScrollsToTop:YES];
//...doSomething...
偽呼叫棧:
- [vc setScrollsToTop]
- self.collectionView.scrollsToTop = scrollsToTop; 觸發get方法
- [self collectionView]
- _collectionView=[[UICollectionView alloc] initWithFrame:self.view.bounds
collectionViewLayout:self.collectionViewLayout]
self.view觸發viewDidLoad - [self viewDidLoad]
- [self.view addSubview:self.collectionView]
self.collectionView重新觸發get - [self collectionView] 再建立新一個例項
- [self.view addSubview:self.collectionView] 返回後addSubview
- _collectionView=[[UICollectionView alloc] initWithFrame:self.view.bounds
collectionViewLayout:self.collectionViewLayout]
返回初次get裡的init方法,返回新例項將上一個例項覆蓋 - self.collectionView.scrollsToTop = scrollsToTop; 此時的collectionView已不是addSubview的那個
- 結束
四、方案
使用成員變數_scrollsToTop儲存值,不要在setScrollsToTop使用self.collectionView
- (void)setScrollsToTop:(BOOL)scrollsToTop {
_scrollsToTop = scrollsToTop;
_collectionView.scrollsToTop = scrollsToTop;
}
在get方法中建立collectionView時,再將_scrollsToTop重新設定回去_collectionView.scrollsToTop = _scrollsToTop
總結:勿濫用懶載入
像例子裡面這個,業務場景肯定是要有collectionView的,這種必要view的初始化根本就沒必要懶載入,因為這個是必要的元素,用_變數隱藏好就行。
懶載入應該是用在一些展示時非必要(即不是每次開這個頁面都會有)、耗效能的UI或容器類上面。尤其是這種對外暴露的方法,呼叫內部變數時更應該慎用懶載入。