英超联赛下注 iOS底层原理

界面优化无非就是解决卡顿问,优化界面流畅度,以下就始末先分析卡顿的因为英超联赛下注,然后再介绍详细的优化方案,来分析如何做界面优化。

点击“直播海南”关注公众号获取最新信息

【环球网报道 记者 徐璐明】日本防卫省称,日本自卫队近日在对马岛附近发现一艘向日本海方向航行的中国军舰,日本出动舰机进行监视。

【环球网军事报道】美国总统国家安全顾问杰克∙沙利文当地时间8月17日表示,美国向阿富汗政府军提供的武器中,很大一部分落到了塔利班手中。

界面渲染流程

详细流程能够参考图片渲染初探[1]这边就也许讲一下图片渲染的流程,大体上能够分为三个阶段就是 CPU处理阶段 GPU处理阶段和视频限制器表现阶段。

大致流程图解如下:

苹果为晓畅决图片扯破的题目行使了 VSync + 双缓冲区的式样,就是表现器表现完善一帧的渲染的时候会向 发送一个垂直信号 VSync,收到这个这个垂直信号之后表现器开起读取另外一个帧缓冲区中的数据而 App接到垂直信号之后开起新一帧的渲染。

CPU主要是计算出必要渲染的模型数据 GPU主要是按照 CPU挑供的渲染模型数据渲染图片然后存到帧缓冲区 视频限制器冲帧缓冲区中读取数据末了成像 卡顿原理

始末上文张的界面渲染流程清新,在图一帧渲染完善之后会发送一个垂直信号此时开起读取另外一个帧缓冲区中的数据,添入此时 CPU和 GPU的做事还异国完善,也就是另外一个帧缓冲区照样添锁状态异国数据的时候,此时表现器表现的照样上一帧的图像那么这栽情况就会不息期待下一帧绘制完善然后视频限制器再读取另外一个帧缓冲区中的数据然后成像,中间这个期待的过程就造成了失踪帧,也就是会卡顿。

卡顿图解如下:英超联赛下注

这栽情况随会造成卡顿

卡顿检测 1.FPS监控

苹果的iPhone保举的刷新率是60Hz,也就是每秒中刷新屏幕60次,也就是每秒中有60帧渲染完善,差不众每帧渲染的时间是1000/60 = 16.67毫秒整个界面会比较流畅,清淡刷新率矮于45Hz的就会展现清晰的卡顿表象。这边能够始末YYFPSLabel来实现FPS的监控,该原理主要是仰仗 CADisplayLink来实现的,始末CADisplayLink来监听每次屏幕刷新并获取屏幕刷新的时间,然后行使次数(也就是1)除以每次刷新的时间阻隔得到FPS,详细源码如下:

#import "YYFPSLabel.h" #import "YYKit.h"  #define kSize CGSizeMake(55, 20)  @implementation YYFPSLabel {   CADisplayLink *_link;   NSUInteger _count;   NSTimeInterval _lastTime;   UIFont *_font;   UIFont *_subFont;    NSTimeInterval _llll; }  - (instancetype)initWithFrame:(CGRect)frame {   if (frame.size.width == 0 && frame.size.height == 0) {       frame.size = kSize;   }   self = [super initWithFrame:frame];    self.layer.cornerRadius = 5;   self.clipsToBounds = YES;   self.textAlignment = NSTextAlignmentCenter;   self.userInteractionEnabled = NO;   self.backgroundColor = [UIColor colorWithWhite:0.000 alpha:0.700];    _font = [UIFont fontWithName:&@quot;Menlo" size:14];   if (_font) {       _subFont = [UIFont fontWithName:&@quot;Menlo" size:4];   } else {       _font = [UIFont fontWithName:&@quot;Courier" size:14];       _subFont = [UIFont fontWithName:&@quot;Courier" size:4];   }    //YYWeakProxy 这边行使了虚拟类来解决强引用题目   _link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(tick:)];   [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];   return self; }  - (void)dealloc {   [_link invalidate]; }  - (CGSize)sizeThatFits:(CGSize)size {   return kSize; }  - (void)tick:(CADisplayLink *)link {   if (_lastTime == 0) {       _lastTime = link.timestamp;       NSLog(&@quot;sdf");       return;   }    //次数   _count++;   //时间   NSTimeInterval delta = link.timestamp - _lastTime;   if (delta < 1) return;   _lastTime = link.timestamp;   float fps = _count / delta;   _count = 0;    CGFloat progress = fps / 60.0;   UIColor *color = [UIColor colorWithHue:0.27 * (progress - 0.2) saturation:1 brightness:0.9 alpha:1];    NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:&@quot;%d FPS",(int)round(fps)]];   [text setColor:color range:NSMakeRange(0, text.length - 3)];   [text setColor:[UIColor whiteColor] range:NSMakeRange(text.length - 3, 3)];   text.font = _font;   [text setFont:_subFont range:NSMakeRange(text.length - 4, 1)];    self.attributedText = text; }  @end 

FPS只用在开发阶段的辅助性的数值,由于他会屡次唤醒 runloop倘若 runloop在闲置的状态被 CADisplayLink唤醒则会消耗性能。

2.始末RunLoop检测卡顿

始末监听主线程 Runloop一次循环的时间来判定是否卡顿,这边必要相符作行使 GCD的信号量来实现,竖立初起化信号量为0,然后开一个子线程期待信号量的触发,也是就是在子线程的手段内里调用 dispatch_semaphore_wait手段竖立期待时间是1秒,然后主线程的 Runloop的 Observer回调手段中发送信号也就是调用 dispatch_semaphore_signal手段,此往往间能够置为0了,倘若是期待时间超时则望此时的 Runloop的状态是否是 kCFRunLoopBeforeSources或者是 kCFRunLoopAfterWaiting,倘若在这两个状态下两秒则表明有卡顿,详细代码如下:(代码中也有有关的注解)

#import "LGBlockMonitor.h"  @interface LGBlockMonitor (){   CFRunLoopActivity activity; }  @property (nonatomic, strong) dispatch_semaphore_t semaphore; @property (nonatomic, assign) NSUInteger timeoutCount;  @end  @implementation LGBlockMonitor  + (instancetype)sharedInstance {   static id instance = nil;   static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{       instance = [[self alloc] init];   });   return instance; }  - (void)start{   [self registerObserver];   [self startMonitor]; }  static void CallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {   LGBlockMonitor *monitor = (__bridge LGBlockMonitor *)info;   monitor->activity = activity;   // 发送信号   dispatch_semaphore_t semaphore = monitor->_semaphore;   dispatch_semaphore_signal(semaphore); }  - (void)registerObserver{   CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};   //NSIntegerMax : 优先级最幼   CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,                                                           kCFRunLoopAllActivities,                                                           YES,                                                           NSIntegerMax,                                                           &CallBack,                                                           &context);   CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); }  - (void)startMonitor{   // 创建信号c   _semaphore = dispatch_semaphore_create(0);   // 在子线程监控时长   dispatch_async(dispatch_get_global_queue(0, 0), ^{       while (YES)       {           // 超往往间是 1 秒,异国等到信号量,st 就不等于 0, RunLoop 一切的义务           // 异国授与到信号底层会先对信号量进走减减操作,此时信号量就变成负数           // 因而开起进入等到,等达到了期待时间还异国收到信号则进走添添操作复原信号量           // 实走进入期待的手段dispatch_semaphore_wait会返回非0的数           // 收到信号的时候此时信号量是1  底层是减减操作,此时刚益等于0 因而直接返回0           long st = dispatch_semaphore_wait(self->_semaphore, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));           if (st != 0)           {               if (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting)               {                   //倘若不息处于处理source0或者批准mach_port的状态则表明runloop的这次循环还异国完善                   if (++self->_timeoutCount < 2){                       NSLog(&@quot;timeoutCount==%lu",(unsigned long)self->_timeoutCount);                       continue;                   }                   // 倘若超过两秒则表明卡顿了                   // 一秒旁边的衡量尺度 很大能够性不息来 避免大周围打印!                   NSLog(&@quot;检测到超过两次不息卡顿");               }           }           self->_timeoutCount = 0;       }   }); }    @end 
3.微信matrix

此方案也是借助 runloop实现的大体流程和方案三相通,不过微信添入了堆栈分析,能够定位到耗时的手段调用堆栈,因而必要实在的分析卡顿因为能够借助微信matrix来分析卡顿。自然也能够在方案2中行使 PLCrashReporter这个开源的第三方库来获取堆栈新闻

4.滴滴DoraemonKit

实现方案也许就是在子线程中不息 ping主线程,在主线程卡顿的情况下,会展现断在的无回响反映的外现,进而检测卡顿

优化方案

上文平分析卡顿的因为吾们清新主要就是在 CPU和 GPU阶段占用时间太长导致了失踪帧卡顿,因而界面优化主要做事就是给 CPU和 GPU减负

预排版

预排版主要是对 CPU进走减负。

倘若现在又个 TableView其中必要按照每个 cell的内容来定 cell的高度。吾们清新 TableView有重用机制,倘若复用池中有数据,即将滑入屏内的 cell就会行使复用池内的 cell,做到撙节资源,但是照样要按照新数据的内容来计算 cell的高度,重新组织新 cell中内容的组织 ,如许逆复滑动 TableView相通的 cell就会逆复计算其 frame,如许也给 CPU带来了义务。倘若在得到数据创建模型的时候就把 cell frame算出,TableView返回模型中的 frame如许的话同样的一条 cell就算来回逆复滑动 TableView,计算 frame这个操作也就仅仅只会实走一次,因而也就做到了减负的功能,如下图:一个 cell的构成必要 modal找到数据,也必要 layout找到这个 cell如何组织:

预解码 & 预渲染

图片的渲染流程,在 CPU阶段拿到图片的顶点数据和纹理之后会进走解码生产位图,然后传递到 GPU进走渲染主要流程图如下

倘若图片许众很大的情况下解码做事就会占用主线程 RunLoop导致其他做事无法实走比如滑动,如许就会造成卡顿表象,因而这边就能够将解码的做事放到异步线程中不占用主线程,能够有人会想只要将图片添载放到异步线程中在异步线程中生成一个 UIImage或者是 CGImage然后再主线程中竖立给 UIImageView,此时能够写段代码行使 instruments的 Time Profiler查望一下堆栈新闻

发现图片的编解码照样在主线程。针对这栽题目常见的做法是在子线程中先将图片绘制到CGBitmapContext,然后从Bitmap 直接创建图片,例如SDWebImage三方框架中对图片编解码的处理。这就是Image的预解码,代码如下:

dispatch_async(queue, ^{  CGImageRef cgImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self]]].CGImage;  CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage) & kCGBitmapAlphaInfoMask;   BOOL hasAlpha = NO;  if (alphaInfo == kCGImageAlphaPremultipliedLast ||      alphaInfo == kCGImageAlphaPremultipliedFirst ||      alphaInfo == kCGImageAlphaLast ||      alphaInfo == kCGImageAlphaFirst) {      hasAlpha = YES;  }   CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;  bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;   size_t width = CGImageGetWidth(cgImage);  size_t height = CGImageGetHeight(cgImage);   CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, CGColorSpaceCreateDeviceRGB(), bitmapInfo);  CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);  cgImage = CGBitmapContextCreateImage(context);   UIImage * image = [[UIImage imageWithCGImage:cgImage] cornerRadius:width * 0.5];  CGContextRelease(context);  CGImageRelease(cgImage);  completion(image); }); 
按需添载

顾名思义必要表现的添载出来,不必要表现的添载,例如 TableView中的图片滑动的时候不添载,在滑动停留的时候添载(能够行使Runloop,图片绘制竖立 defaultModal就走)

异步渲染

再说异步渲染之前先晓畅一下 UIView和 CALayer的有关:

UIView是基于 UIKit框架的,能够批准点击事件,处理用户的触摸事件,并管理子视图 CALayer是基于 CoreAnimation,而CoreAnimation是基于QuartzCode的。因而CALayer只负责表现,不克处理用户的触摸事件 UIView是直接继承 UIResponder的,CALayer是继承 NSObject的 UIVIew 的主要职责是负责授与并回响反映事件;而 CALayer 的主要职责是负责表现 UI。UIView 倚赖于 CALayer 得以表现

总结:UIView主要负责时间处理,CALayer主要是视图表现 异步渲染的原理其实也就是在子线程将一切的视图绘制成一张位图,然后回到主线程赋值给 layer的 contents,例如 Graver框架的异步渲染流程如下:

核心源码如下:

if (drawingFinished && targetDrawingCount == layer.drawingCount) {   CGImageRef CGImage = context ? CGBitmapContextCreateImage(context) : NULL;   {       // 让 UIImage 进走内存管理       // 最后生成的位图         UIImage *image = CGImage ? [UIImage imageWithCGImage:CGImage] : nil;       void (^finishBlock)(void) = ^{           // 由于block能够在下一runloop实走,再进走一次检查           if (targetDrawingCount != layer.drawingCount)           {               failedBlock();               return;           }           //主线程中赋值完善表现           layer.contents = (id)image.CGImage;           // ...       }       if (drawInBackground) dispatch_async(dispatch_get_main_queue(), finishBlock);       else finishBlock();   }    // 一些修整做事: release CGImageRef, Image context ending } 

最闭幕果图如下:

其他 缩短图层的层级 缩短离屏渲染 图片表现的话图片的大幼竖立(不要太大) 少行使addView 给cell动态增补view 尽量避免行使透明view,由于行使透明view,会导致在GPU中计算像素时,会将透明view基层图层的像素也计算进来,即颜色同化处理(当有两个图层的时候一个是半透明一个是不透明倘若半透明的层级更高的话此时就会触发颜色同化,底层的同化并不是仅仅的将两个图层叠添而是会将两股颜色同化计算出新的色值表现在屏幕中)

posted on 2021-09-09  作者:admin  阅读量:

栏目导航

Powered by 英超联赛下注 @2018 RSS地图 HTML地图