绕开Live SDK内存释放bug和实现延迟自动刷新
之前,上一个版本的Live SDK各种内存泄露的bug弄得我比较烦躁,程序闪退崩溃,还要把后台的其他程序的内存都全部占用了。这太不科学了,但是新版本SDK又出现了 一个新问题,就是调用delegate的方法时不检查对象是否已经被dealloc了。我估计它用的是__unsafe_unretained的指针,所以即使对象释放了,指针也不会变为nil。然后就不管3721直接调用该对象的delegate方法,那么程序肯定会马上出错了。但这种错误在上一版SDK上是不会出现的,因为上一版的内存泄露导致controller不会dealloc,同时也不会显现出调用delegate方法出错的问题。
一、内存释放bug
那么解决问题的方法,我只能够模仿上一版本的SDK漏洞,就是通过循环引用,避免controller在调用delegate方法之前就被dealloc。
@property (strong, nonatomic) EarthViewController *noDealloc
在调用SDK的API之前,先将noDealloc = self,那么就会因为循环引用,controller无法被dealloc。然后只要在- (void) liveOperationSucceeded:(LiveOperation *)operation通过userState来将noDealloc = nil。那么controller也能够正常释放,再也不会因为用户操作过快而导致奔溃。因为这些controller都用用来浏览文件的,浏览的时候如果用户在刷新但是又没有耐性等,直接就将contrlloer pop了,那么就会出现上面的问题。
二、延迟刷新
为了保证能看到更新后SkyDrive上的文件,总是要用户刷新也太不人性化了,所以我加了个自动刷新的功能。一开始我是直接在- (void) viewWillAppear:(BOOL)animated里实现,不过一出现页面就刷新的话,会出现很多问题。例如,在快速不断后退目录的时候,途中经过的目录也会不断刷新,造成最终应该要刷新的目录刷新龟速。同时,这样做也浪费了大量的流量,耗费内存,影响了正在上传和下载任务的速度。
后来我改进了方法,思路就是延迟几秒刷新,不过Objective-C似乎没有,可以延迟执行block的函数。所以先自己实现一个
1 @implementation NSObject (PerformBlockAfterDelay) 2 3 4 - (void)performBlock:(void(^)(void))block afterDelay:(NSTimeInterval)delay 5 6 { 7 [self performSelector:@selector(fireBlockAfterDelay:) withObject:block afterDelay:delay]; 8 } 9 10 - (void)fireBlockAfterDelay:(void(^)(void))block 11 { 12 13 block(); 14 15 } 16 @end
方法很简单,不过很实用。然后就实现刷新的调用
1 [self performBlock:^{ 2 [UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; 3 [liveClient getWithPath:[NSString stringWithFormat:@"%@/files?sort_by=name", self.URLPath] 4 delegate:self 5 userState:@"URLPath"]; 6 } 7 afterDelay:3];
不过这样做,还是有问题,实际上所有的controller还是会在3秒后就执行刷新,无论你是否离开了当前这个目录。也就是说,目录无论如何都会刷新,只不过是延迟了3秒。这样实在太糟糕了,虽然也减轻了小部分刷新的压力,但是还是会做无谓的刷新,例如多次后退到同一页面,该页面就进行多次反复刷新。所以要优化一下刷新的思路,保证同一时间不会调用一次以上的刷新,正在刷新的时候也不能重复调用刷新。
于是,我现在的方法是,先定义一个全局变量EarthViewController *currentController,每一次进入到目录时,就先将这个变量赋值为self。那么,这就可以知道当前的controller是哪一个了,当然可以用其他方法实现,方法不唯一。
1 self.title = self.fileTitle; 2 noDeallocURLPath = self; 3 currentController = self; 4 5 //自动刷新 6 if (self.cellsArray) { 7 8 [self performBlock:^{ 9 if (self == currentController && _reloading == NO) { 10 [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; 11 _reloading = YES; 12 [liveClient getWithPath:[NSString stringWithFormat:@"%@/files?sort_by=name", self.URLPath] 13 delegate:self 14 userState:@"URLPath"]; 15 } else 16 noDeallocURLPath = nil; 17 } 18 afterDelay:3]; 19 20 } else { 21 22 [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; 23 _reloading = YES; 24 [liveClient getWithPath:[NSString stringWithFormat:@"%@/files?sort_by=name", self.URLPath] 25 delegate:self 26 userState:@"URLPath"]; 27 28 } 29 30 [_refreshHeaderView refreshLastUpdatedDate];
由于,现时尚未实现缓存,所以每次进入到新目录就立即进行刷新。至于旧的目录,就继续延迟刷新,_reloading是下拉刷新库的一个变量,负责判断现在是否正在刷新中。同时判断,是否为当前目录,否就不刷新,不浪费资源,因为SDK同时处理请求的能力是有限的。刷新完成后就老规矩,将该还原的变量都全部设回一个正常值。