懒加载的坑

来源:互联网 发布:python 语音处理 编辑:程序博客网 时间:2024/04/28 23:09

欢迎访问我的个人blog
https://ekulelu.github.io/

这次我们来讲讲懒加载。懒加载的意思就是将成员变量的创建放到getter方法里面。使用到这个成员变量的时候再进行创建。这样可以节省内存空间。这里有个主意点:以后使用懒加载的成员变量的时候,请用getter方法,而不是 +成员变量名字 。因为 +成员变量名字 这种写法并不会调用getter方法,所以可能会拿到空的成员变量。 Getter和Setter方法的写法这里就不讲了。这里讲的是各种不经意的写法混合到一起产生的怪异bug。

对于一个UIView或UIViewController里面的控件类型的成员变量。一般是建议使用懒加载来加载的。这里我们举一个在UIViewController里面懒加载imageView为例。但控件是要添加到主View里面的,所以一般创建后还需要调用

 [self.view addSubview:self.imgView];

这句话应该放到哪里呢?一般有3个选择:viewDidLoad,initWithFrame,懒加载方法。现在我们用到了懒加载,放到懒加载里面似乎是个不错的选择。那就先放到懒加载里面。这里还有一个问题,子View的持有属性问题,一般来讲,将子View添加到主View之后,主View会对它的引用计数+1,那么属性那里是可以设置为weak的,当然,设置为strong也没问题。
先讨论设置为weak的情况。

@property (weak, nonatomic) UIImageView *imgView;

设置为weak后,那么在懒加载中创建变量的时候,就不能将这个变量直接赋值给_imgView,因为weak熟悉不会持有,变量创建后就会销毁。 所以得这样写

- (UIImageView *)imgView{    NSLog(@"get imgView");    if (_imgView == nil) {        UIImageView *imgView = [[UIImageView alloc] init];        _imgView = imgView;        [self.view addSubview:_imgView];    }    return _imgView;} 

好像这个看得不太优雅,换成下面的写法看看

- (UIImageView *)imgView{    NSLog(@"get imgView");    if (_imgView == nil) {        UIImageView *imgView = [[UIImageView alloc] init];        [self.view addSubview:_imgView=imgView];    }    return _imgView;} 

这两种写法有没区别呢?看着好似没。

既然我们是一个imgView,这个imgView里面的image可能是从别的类传过来的。那么提供一个接口给外部传递进来是必须的。所以在头文件里面声明了

// .h 文件- (void)setImage:(UIImage*)image;

然后在.m文件里面写实现,把传进来的image直接赋值给self.imgView。

// .m文件- (void)setImage:(UIImage*)image{    NSLog(@"set img");    self.imgView.image = image;}

对了,我们还没给self.imgView的frame赋值呢。所以在viewDidLoad里面赋值。

//viewDidLoadself.imgView.frame = CGRectMake(0, 100, 100, 100);

现在一切看起来都没问题的样子,那么我们就把这个ViewController给push出来看看

ViewController *vc = [[ViewController alloc ] init];[vc setImage:[UIImage imageNamed:@"home"]];[self.navigationController pushViewController:vc animated:YES];

恭喜,你被成功绕进去了,这个push出来的ViewController一片空白。这究竟是怎么回事呢?我们来跟一下代码执行情况。
首先是

[vc setImage:[UIImage imageNamed:@"home"]];

这句会调用到setImage里面的 self.imgView ,然后这会激活到懒加载方法。之后到了下面这句话,这句是关键。

[self.view addSubview:_imgView=imgView];

到了这里此刻self.view是空的!所以首先会对view进行创建,然后创建的过程中会执行到viewDidLoad方法。注意,我们在viewDidLoad里面对self.imgView的frame进行了赋值,这又会激活了imgView的懒加载方法,此刻_imgView仍然是空的,所以又再次进行了创建对象的方法,注意,此时创建又了一个新的imgView对象。然后继续到了下面这句,注意,这是第二次进入。

[self.view addSubview:_imgView=imgView];

此时self.view已经有值了。所以接着执行_imgView=imgView,此刻 _imgView终于有值了,但是这个对象是第二次创建的imgView,并将它添加到了self.view里面。随后self.imgView的懒加载完成。回退到viewDidload里面的设置frame方法里面。这个时候对self.imgView的frame进行赋值。
然后viewDidLoad执行完毕,回退到第一次的self.imgView的懒加载方法里面的下面这句

[self.view addSubview:_imgView=imgView];

此时的imgView是第一次创建的imgView。然后执行_imgView=imgView。看到了吗,这里又进行了一次对 _imgView的赋值,此时赋值的是第一次创建的imgView,然后再添加到self.view里面。随后退出imgView的懒加载方法,然后再回到setImage方法里面对这个self.imgView进行image赋值。
留意到没?这个第一次创建的imgView并没有进行frame的赋值,所以显示不出来。
而且这里创建了两个imgView,并且两个都添加到了self.view里面。如果你打印一下self.view.subviews.count你就知道了。

下面说说解决方法
1、你可以把懒加载方法写回下面这种不“优雅”得到形式,这样的话,在viewDidLoad里面调用到懒加载后,此时的_imgView已经有值了,就不会在进行创建。但是其实这里还是会对view进行加载,如果你viewDidLoad里面有其他的一些方法关联到了另外的一些子控件的初始化,可能会导致一些不可控的后果。因此不推荐这种方法。

- (UIImageView *)imgView{    NSLog(@"get imgView");    if (_imgView == nil) {        UIImageView *imgView = [[UIImageView alloc] init];        _imgView = imgView;        [self.view addSubview:_imgView];    }    return _imgView;} 

2、不要在view加载前去访问子控件。但是这个约束得我们自己去遵守,如果你写的代码是给别人用的话,这个规则不能确保别人会遵守。而且如果不访问的话,那么就像刚刚设置需要image的话,还得再创建一个属性来保存这个image,很麻烦。
3、不要在懒加载里面去访问self.view。这个是我个人比较推荐的。addsubview方法可以放到viewDidLoad里面或这个方法执行之后。

0 0
原创粉丝点击