对于app在使用过程中出现卡顿的情况应该算是比较常见的,这里说的卡顿指的是界面流畅度上的卡顿。
那卡顿是怎么产生的?
app启动后的main函数会默认启动一个runloop来接收用户事件来进行UI更新核其它操作
runloop的运行机制简单来说大概是这样的,一个线程在原地不停循环执行,当有事件源需要处理时候就唤醒runloop所在线程,然后跑去执行一下任务,完事后就进入休眠等待下一次事件源。
而UI操作和更新是有一系列时间片间隔很短的事件源组成,正常情况下是快速的处理完一个然后休眠紧接着又唤醒处理下一次,当中间某些个操作执行了过多的其它事情或耗时操作时候就会导致原本需要紧接着进行更新的UI事件被破延迟,这就导致了视觉上的卡顿感,所以很多情况下会把一些不需要主线程处理的事情或耗时操放倒子线程去执行,完了后再切换回主线程。典型的YYLabel就是这么干的。
然而当碰到一堆事件(比较耗时)就是需要在主线程上处理的时候,就不能简单的进行线程切换来处理了,这时候可以运用到runloop执行的特性来进行优化。不要一次性跑完这一堆事件,而是把分散开去执行,在runloop即将进入休眠时候跑一个,下次再即将休眠时候又跑一个。
比方说一个UI事件执行时间需要0.1秒(假设)这一堆耗时操作又10个,每个需要0.05秒,一共0.5秒。
A无耗时UI更新过程:0.1 -> 0.1 -> 0.1 ...0.1;
B有耗时不分开执行:0.1 -> 0.1+0.5 -> 0.1... 0.1;
C有耗时分开执行行:0.1 -> 0.1+0.05 -> 0.1+0.05 -> 0.1+0.05 -> 0.1+0.05 -> 0.1+0.05 ... 0.1;
这个描述应该比较直观
简单来讲就一句话,不要在有UI操作时候过多占用主线程。
关键函数如下:
监听主函数runloop状态变化
-(void)_addObserverIfNeed {
static const CFIndex kErgodicOrder = 0;//优先级
if (!_ergodicObserver) {
__weak typeof(self) wself = self;
_ergodicObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting | kCFRunLoopExit, YES, kErgodicOrder, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
[wself _ergodicProcess];
});
if (_ergodicObserver) {
CFRunLoopAddObserver(CFRunLoopGetMain(), _ergodicObserver, kCFRunLoopCommonModes);
}
}
}
执行等待的操作
- (void)_ergodicProcess {
for (NSUInteger i = 0; i<_ergodicArr.count; i++) {
EArrElement *eArrElement = _ergodicArr[i];
if (eArrElement.idx < eArrElement.arr.count) {
BOOL isContinue = eArrElement.element(eArrElement.idx, eArrElement.arr[eArrElement.idx++]);
if (!isContinue || eArrElement.idx == eArrElement.arr.count) {
pthread_mutex_lock(&_lock);
[_ergodicArr removeObject:eArrElement];
pthread_mutex_unlock(&_lock);
i--;
}
}else {
pthread_mutex_lock(&_lock);
[_ergodicArr removeObject:eArrElement];
pthread_mutex_unlock(&_lock);
i--;
}
}
for (NSString* key in _ergodicKeyObj.allKeys) {
NSMutableArray <EObjElement*>* ergodicObj = _ergodicKeyObj[key];
for (NSUInteger i = 0; i<ergodicObj.count; i++) {
EObjElement *eObjElement = ergodicObj[i];
pthread_mutex_lock(&_lock);
[ergodicObj removeObject:eObjElement];
pthread_mutex_unlock(&_lock);
i--;
BOOL isContinue = eObjElement.element(key, eObjElement.obj);
pthread_mutex_lock(&_lock);
if (!isContinue || ergodicObj.count == 0) {
[_ergodicKeyObj removeObjectForKey:key];
}
pthread_mutex_unlock(&_lock);
}
}
pthread_mutex_lock(&_lock);
if (_ergodicArr.count == 0 && _ergodicKeyObj.allKeys.count == 0) {
[self _clearObserverIfAllow];
}
pthread_mutex_unlock(&_lock);
}
由于函数调用可能涉及多线程,所以在关键点代码位置需要增加线程锁来控制并发导致的一些错误情况。
ps:有描述不当还请留言提醒。