知识共享许可协议本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。本文仅作为个人学习记录使用,欢迎在许可协议范围内转载或使用,请尊重版权并且保留原文链接,谢谢您的理解合作。如果您觉得本站对您能有帮助,您可以使用RSS方式订阅本站,这样您将能在第一时间获取本站信息。

问题现象

最近一个项目,需要使用NSTableView显示一个列表,列表上显示用户头像和用户信息。在用户很少的时候,也就是item很少的时候,没啥问题,但是找了朋友的帐号来测试的时候,他的账号下有八百多个用户,这就让这个程序出现诡异问题了。在快速滚动的时候,出现了一个现象,当滚动停止时,列表上的头像不断的在变化,一直到变化到一段时间后,才会停止。

初步分析这个问题很奇怪,不过从问题的表象上来看,似乎是图片刷新的问题,Cell的文字部分似乎是不存在这个问题的。当然经过google了一段时间后发现,大家都是搞的UITableView的-,-UI**的。。。iOS这么火,Mac开发快成没人爱的孩子了。。。好吧,那还是自己继续研究了。

问题分析

分析问题,个人比较喜欢的就是观察现象,之后进行思考和分析,那么再次回到表象后,仔细观察后发现图片的刷新那都是有规律的,滑动的越多那么刷新的次数就越多,慢速滑动的时候却不会出现这样的情况。那么再次猜测是否是刷新很慢的问题?因为网络连接很慢,那么会不会是导致了图片在不断的刷新?但是总觉得每一个View应该是独立的,并且每次创建View的时候,我都把图片设为了nil,但是依然存在这个问题,这时候才想起来了NSTableView有一个View重用的机制,如果一个View走出了可视区,并且进入了reuse queue的话,那么他就会被重用,基于上面的观察和思考后,再次分析代码,终于发现了问题的症结所在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
+ (void)downloadImageWithURLString:(NSString *)urlString onComplate:(void (^)(NSImage *image))complate
{
if (!urlString) {
return;
}
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]] success:complate];
[operation start];
}

//使用时是这样的
- (void)initWithObj:(Obj *)obj
{
// Other init code
// ...

[self.myImage setImage:nil];
[MyTableCellView downloadImageWithURLString:obj.imgurl onComplate:^(NSImage *image){
[self.myImage setImage:image];
}];

// ...
// Other init code
}

上面是获取图片的代码,这样,每次加载的时候都会先将myImage设置为nil,之后通过AFImageRequestOperation异步的获取。这么看似乎是没有问题的,因为每次使用之前我都设置为了nil,之后异步的从网络获取图片并加载。那么结合了NSTableView的View重用机制后,就会是下面这样的情况。

假设我有很多记录,并且每次都只显示其中的10个记录,并且我的网速不慢不快,那么从1快速滑动到600或者更多,那么在滑动过程中,某个View被重用了,并重新加载了很多次(方便点就认为是10次吧),那么会发生下面现象:

  1. 加载需要显示的东西(文字啊,名字啊,年龄啊之类的东西)
  2. 建立AFImageRequestOperation并启动获取图像的进程。
  3. 滑出显示区外,进入reuse queue。
  4. 再次进入显示区域,进入第一步。

就这样,每次重用时都发生了上面的过程,如果你的网速非常快(0秒加载),那上面的代码妥妥的。但是肯定很多童鞋跟我一样,在可怜的2M小水管下,并且每个图片都需要穿越这伟大的防火长城,那必须得卡,必须出错。

上面的过程中,AFImageRequestOperation完成后,就会马上执行complate的代码,马上刷新图片,那么如果已经滑倒600了,我才把图片下载完,那肯定是顺着下载完成的顺序,不断把myImage刷新10次,这样就看到了问题表象,图片象是放幻灯片一样的,不断切换了。

解决思路

问题原因找到了,那就好解决了,借用让子弹飞里面一句话,狠一点“杀人要诛心”啊,不但要把你的图片干掉(设成nil),还要让你断了那个想念(继续从网络加载图片),接下来我们看看怎么做的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@interface MyTableCellView : NSTableCellView
// 先把心留下来,不然没法诛
@property (strong, nonatomic) AFImageRequestOperation *myImageDownloadOperation;
@end

// 让你有想念的时候,那把想念记下来
+ (AFImageRequestOperation *)downloadImageWithURLString:(NSString *)urlString onComplate:(void (^)(NSImage *image))complate
{
if (!urlString) {
return nil;
}
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]] success:complate];
return operation;
}

//使用时是这样的
- (void)initWithObj:(Obj *)obj
{
// Other init code
// ...

if (self.myImageDownloadOperation) {
// 杀人
[self.myImage setImage:nil];
// 诛心
[self.myImageDownloadOperation cancel];
}

self.myImageDownloadOperation = [MyTableCellView downloadImageWithURLString:obj.imgurl onComplate:^(NSImage *image){
[self.myImage setImage:image];
}];
if (self.myImageDownloadOperation) {
[self.myImageDownloadOperation start];
}

// ...
// Other init code
}

总结

嗯嗯,总结下,刚好领导再开XXXXXX检查总结会,我也来总结下分析问题还是要从现象入手,结合平台的资料,再分析代码,才能找到问题的根源,并且也是因为自己对用的东西不是那么熟悉导致的。

也希望将来在Mac上耕耘的童鞋更多啦~~

最后欢迎大家订阅我的微信公众号 Little Code

公众号

  • 公众号主要发一些开发相关的技术文章
  • 谈谈自己对技术的理解,经验
  • 也许会谈谈人生的感悟
  • 本人不是很高产,但是力求保证质量和原创