当设计到大量数据的时候,比如说,直播的聊天。我们处理的时候,可以考虑三点:
- 分多次处理
- kCFRunLoopBeforeWaiting 闲时处理(在进入等待前,也就是说现在已经不忙了,才会要进入等待)
- 频率问题
代码如下:
#import "TwoLargeDataVC.h"
@interface TwoLargeDataVC (){
CFRunLoopObserverRef observerRef;
}
@property (nonatomic, strong)IBOutlet UITableView *tableView;
@property (nonatomic, strong)NSMutableArray *dataArr;
@property (nonatomic, strong)NSMutableArray *newsDataArr;
@property (nonatomic, strong)NSLock *dataLock;
@end
/*
被动接受数据()
*/
@implementation TwoLargeDataVC
- (void)viewDidLoad {
[super viewDidLoad];
// 监听runloop状态 当runloop时候kCFRunLoopBeforeWaiting状态(开始等待前,即这个时候RunLoop已经不忙了),再来处理这个大数据量
_dataArr = [NSMutableArray array];
_newsDataArr = [NSMutableArray array];
_dataLock = [NSLock new];
_tableView.estimatedRowHeight = 0;
_tableView.clipsToBounds = YES;
[_tableView setFrame:CGRectMake(0, 300, _tableView.frame.size.width, 300)];
[self runloopObserver];
// 开启一个线程接收服务器的数据
[NSThread detachNewThreadSelector:@selector(threadDataFromServer) toTarget:self withObject:nil];
}
/*
1 分多次处理
2 kCFRunLoopBeforeWaiting 闲时处理
3 频率问题
*/
// 创建runloop 监听
- (void)runloopObserver{
__weak typeof(self) weakSelf = self;
__block NSTimeInterval timeInterVal = [[NSDate date] timeIntervalSince1970];
// 创建观察者
observerRef = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
// 设置刷新频率 最少0.5秒刷新一次
//来处理数据量 (直播刷留言)
if (activity == kCFRunLoopBeforeWaiting) { // 开始等待前,即这个时候RunLoop已经不忙了
// 控制频率
NSTimeInterval currentTimeInterVal = [[NSDate date] timeIntervalSince1970];
// 小于0.5秒就直接返回,不进行后面的操作
if (currentTimeInterVal - timeInterVal < 0.5) {
return ;
}
timeInterVal = currentTimeInterVal;
// 处理数据的时候要加上锁,保证数据安全
[weakSelf.dataLock lock];
NSArray *subArr = nil;
if (weakSelf.newsDataArr.count > 0) { // 一次最多拿10条数据
// 频率可以通过newsDataArr的数据量来控制(如果数据量大,频率就可以设置快一点,如果数据量小,就可以设置慢一点)
NSRange range;
if (weakSelf.newsDataArr.count >= 10) {
range = NSMakeRange(0, 10);
}else{
range = NSMakeRange(0, weakSelf.newsDataArr.count-1);
}
// 拿出数据subArr
subArr = [weakSelf.newsDataArr subarrayWithRange:range];
// 将拿出的数据subArr从新数据中移除,避免下次还是会取到它
[weakSelf.newsDataArr removeObjectsInRange:range];
}
// 将拿到的数据subArr添加到数据源中
[weakSelf.dataArr addObjectsFromArray:subArr];
[weakSelf.dataLock unlock]; // 数据操作结束,解锁
[weakSelf.tableView reloadData]; // 刷新界面
[weakSelf.tableView layoutIfNeeded];
// 始终展示最后一页的数据,即最新的数据 tableView.contentSize.height 会每次都变大。所以ContentOffset的位置也要调整
[weakSelf.tableView setContentOffset:CGPointMake(0, weakSelf.tableView.contentSize.height - weakSelf.tableView.frame.size.height)];
}
});
// 添加观察者
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observerRef, kCFRunLoopCommonModes);
}
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
// 移除观察者
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observerRef, kCFRunLoopCommonModes);
}
// 接收服务器数据
- (void)threadDataFromServer{
// 要注释了这个方法才会执行dealloc方法,因为下面是while(1),线程永远不会结束
// 换成正常的网络请求,当请求结束,线程也就会结束的
static int __countMsg = 0;
while (1) {
sleep(1);
[_dataLock lock];
for (int i = 0; i < random()%1000; i++) { // 生成1到999条不等的数据量
__countMsg++;
NSString *msgStr = [NSString stringWithFormat:@"%d---%ld", __countMsg, random()];
[self.newsDataArr addObject:msgStr];
}
[_dataLock unlock];
// 数据请求完成过后,要激活RunLoop来处理,这样才能将线程和RunLoop关联起来
CFRunLoopWakeUp(CFRunLoopGetMain());
}
}
#pragma mark - tableView delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return _dataArr.count;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 30;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
cell.textLabel.text = _dataArr[indexPath.row];
return cell;
}
- (void)dealloc{
NSLog(@"%s", __func__);
}
@end
上面需要注意的是:
- 处理数据的时候,要加锁。避免数据出问题。
- 使用多线程请求数据的时候,当请求完成过后要唤醒主RunLoop
- 页面消失的时候,要移除观察者