【Bug记录】懒加载的坑

1 篇文章 0 订阅

一、问题

最近开发过程中,遇到了一个诡异的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...

伪调用栈:

  1. [vc setScrollsToTop]
  2. self.collectionView.scrollsToTop = scrollsToTop; 触发get方法
  3. [self collectionView]
  4. _collectionView=[[UICollectionView alloc] initWithFrame:self.view.bounds
    collectionViewLayout:self.collectionViewLayout]
    self.view触发viewDidLoad
  5. [self viewDidLoad]
  6. [self.view addSubview:self.collectionView]
    self.collectionView重新触发get
  7. [self collectionView] 再创建新一个实例
  8. [self.view addSubview:self.collectionView] 返回后addSubview
  9. _collectionView=[[UICollectionView alloc] initWithFrame:self.view.bounds
    collectionViewLayout:self.collectionViewLayout]
    返回初次get里的init方法,返回新实例将上一个实例覆盖
  10. self.collectionView.scrollsToTop = scrollsToTop; 此时的collectionView已不是addSubview的那个
  11. 结束

四、方案

使用成员变量_scrollsToTop保存值,不要在setScrollsToTop使用self.collectionView

- (void)setScrollsToTop:(BOOL)scrollsToTop {
    _scrollsToTop = scrollsToTop;
    _collectionView.scrollsToTop = scrollsToTop;
}

在get方法中创建collectionView时,再将_scrollsToTop重新设置回去_collectionView.scrollsToTop = _scrollsToTop

总结:勿滥用懒加载

像例子里面这个,业务场景肯定是要有collectionView的,这种必要view的初始化根本就没必要懒加载,因为这个是必要的元素,用_变量隐藏好就行。

懒加载应该是用在一些展示时非必要(即不是每次开这个页面都会有)、耗性能的UI或容器类上面。尤其是这种对外暴露的方法,调用内部变量时更应该慎用懒加载。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值