YYWebImage源码分析

YYImage源码

YYModel源码解析

前言

这段时间针对设计架构,解耦以及性能优化相关的知识点看了不少,前两者可以看MVVM和AOP编程思路,最后一个是昨天看的,YY大神的文章之前有看过,但是没有认真研究,这段时间根据源码再结合文章看了下,我真的是服,CF和CT框架都玩的那么6

YY的作者文章也有提到参考的是Facebook的开源框架AsyncDisplayKit,这里还是先看看YYText的源码分析,这东西扩展开来真的消化不了,而且一次性看太多容易吐,太多知识点了,核心思路都差不多,你可以把YYText异步渲染的思路理解为FaceBook ASDK实现的简单思路,把计算排版渲染的任务丢到后台线程,最后回调出来赋值给layer.content显示

- (void)display {dispatch_async(backgroundQueue, ^{CGContextRef ctx = CGBitmapContextCreate(...);// draw in context...CGImageRef img = CGBitmapContextCreateImage(ctx);CFRelease(ctx);dispatch_async(mainQueue, ^{layer.contents = img;});});
}

看吧,大神都说就这个思路了,很简单啊,第一次看他的博客还真信了,就没去看了,现在回头再看

骗子,几千几万行代码,看完之后懵逼了,感觉和看完JSPatch一样,握草,竟然会有这样的人写出这样的代码,感觉自己写的就是一坨屎,赶紧记录下来,学习下知识点

介绍

YYText 是YYKit中的一个富文本显示,编辑组件,拥有YYLabel,YYTextView 两个控件。其中YYLabel类似于UILabel,但功能更为强大,支持异步文本渲染,更丰富的效果显示,支持UIImage,UIView, CALayer 文本附件,自定义强调文本范围,支持垂直文本显示等等。YYTextView 类似UITextView,除了兼容UITextView API,扩展了更多的CoreText 效果属性,支持高亮链接,支持自定义内部文本路径形状,支持图片拷贝,粘贴等等。下面是YYText 与 TextKit 的比较图

图还是要有的,这里可以很清楚的看出来,暴露给外部的YYLabel底部有三个核心,另外还有一个最重要的异步绘制Layer

YYAsyncLayer: YYLabel的异步渲染,通过YYAsyncLayerDisplayTask 回调渲染

YYTextLayout: YYLabel的布局管理类,也负责绘制(排版和绘制)管理下面两个

YYTextContainer: YYLabel的布局类  负责布局形状

NSAttributedString+YYText: YYLabel 所有效果属性设置  负责内容

YYAsyncLayer主要负责Hook disPlay方法,Delegate给YYLabel,让YYLabel的YYTextLayout先排版后根据Task的Block传递CGContextRef进行渲染,最后在YYAsyncLayer的主线程赋值content

YYAsyncLayer 是 CALayer的子类,通过设置 YYLabel 类方法 layerClass
返回自定义的 YYAsyncLayer ,重写了父类的 setNeedsDisplay ,(用来标记内容在下一帧到的时候渲染,执行display) display 实现 contents 自定义刷新。YYAsyncLayerDelegate返回新的刷新任务 newAsyncDisplayTask 用于更新过程回调,返回到 YYLabel 进行文本渲染。其中 YYSentinel是一个线程安全的原子递增计数器,用于判断更新是否取消。

YYTextLayout(负责排版和渲染)

YYLabel 实现了 YYAsyncLayerDelegate 代理方法 newAsyncDisplayTask,回调处理3种文本渲染状态willDisplay ,display,didDisplay 。在渲染之前,移除不需要的文本附件,渲染完成后,添加需要的文本附件。渲染时,首先获取YYTextLayout, 一般包含了 YYTextContainerNSAttributedString 两部分, 分别负责文本展示的形状和内容。不管是渲染时和渲染完成后,最后都需要调用 YYTextLayout的 核心绘制渲染方法

- (void) drawInContext:(CGContextRef)contextsize:(CGSize)sizepoint:(CGPoint)pointview:(UIView *)viewlayer:(CALayer *)layerdebug:(YYTextDebugOption *)debugcancel:(BOOL (^)(void))cancel{

执行逻辑顺序

1.外层调用(YY自己加的NSAttribute属性自己看源码)

非异步

    YYLabel *label = [YYLabel new];label.attributedText = text;label.width = self.view.width;label.height = self.view.height - (kiOS7Later ? 64 : 44);label.top = (kiOS7Later ? 64 : 0);label.textAlignment = NSTextAlignmentCenter;label.textVerticalAlignment = YYTextVerticalAlignmentCenter;label.numberOfLines = 0;label.backgroundColor = [UIColor colorWithWhite:0.933 alpha:1.000];[self.view addSubview:label];

异步

    YYLabel *label = [YYLabel new];label.displaysAsynchronously = YES;label.ignoreCommonProperties = YES;label.backgroundColor = [UIColor blueColor];label.origin = CGPointMake(100, 100);// label.size = CGSizeMake(100, 100); //这段代码貌似会检测上下文,会跑完所有的ui相关才会进行下一步渲染  看看是否有排版// display只有在size改变的时候才会调用--->渲染[self.view addSubview:label];dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// Create attributed string.NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:@"Some Text"];text.yy_font = [UIFont systemFontOfSize:16];text.yy_color = [UIColor grayColor];[text yy_setColor:[UIColor redColor] range:NSMakeRange(0, 4)];// Create text containerYYTextContainer *container = [YYTextContainer new];container.size = CGSizeMake(100, CGFLOAT_MAX);container.maximumNumberOfRows = 0;// Generate a text layout.YYTextLayout *layout = [YYTextLayout layoutWithContainer:container text:text];dispatch_async(dispatch_get_main_queue(), ^{// 1. 只有当空间的Size改变的时候才能调用display// 2. 只有当Label有Size的时候调用排版就会调用Display方法  进行渲染label.size = layout.textBoundingSize;label.textLayout = layout;});});

2.YYLabel 属性setter内部原理

以attributeText为例,setter方法给内部_innerText私有变量赋值 除了基本的setter赋值以为,这里除了backgroundColor,其他setter方法都执行了下面三句代码

        [self _setLayoutNeedUpdate];[self _endTouch];[self invalidateIntrinsicContentSize];

第一句表示设置了该属性都会有flag,表示下一帧刷新的时候需要重绘

- (void)_setLayoutNeedRedraw {[self.layer setNeedsDisplay];
}
+ (Class)layerClass {return [YYTextAsyncLayer class];
}

这里已经把YYLabel的Layer指向了YYTextAsyncLayer(这里重写了Display),标记后,会在刷新的强制所有指向这个Layer的View调用display,自定义渲染

第二句表示停止事件处理

第三句取消当前的size 在下一次布局中采用 - (CGSize)IntrinsicContentSize返回的新的size进行布局,可以理解为生成YYTextLayout进行排版

根据第一段非异步的调用,这里排版是在主线程的

// https://www.jianshu.com/p/515e12728138
/**2. Intrinsic Content SizeIntrinsic这个词的意思,「本质的、固有的」。一个View的Intrinsic Content Size意指这个View想要舒舒服服地显示出来,
需要多大的size。对于一个numberOfLines为0的Label来说,它的preferredMaxLayoutWidth确定、font确定,
则它的intrinsicContentSize就定下来了。不是所有的View都有intrinsicContentSize,在自定义的View中,
可以覆盖intrinsicContentSize方法来返回Intrinsic Content Size,并可以通过调用invalidateIntrinsicContentSize来通知
布局系统在下一个布局过程采用新的Intrinsic Content Size。*/
- (CGSize)intrinsicContentSize {........省略部分代码YYTextContainer *container = [_innerContainer copy];container.size = containerSize;// 通过container的大小 和 富文本 计算出Layout的大小size  排版YYTextLayout *layout = [YYTextLayout layoutWithContainer:container text:_innerText];return layout.textBoundingSize;
}

第二段异步开启之后只要设置两个属性即可,每次执行完setter方法,然后会把之前的content设置为nil,最后还是那执行三句代码

3.SetNeedsDisplay之后调用重写的display方法进行自定义渲染

上面的方法都会进行YYTextLayout的排版和重绘标记,因此就会在刷新的时候进入display

这里有个_displaysAsynchronously属性表示是否开线程进行异步渲染绘制

- (void)display {super.contents = super.contents;[self _displayAsync:_displaysAsynchronously];
}
- (void)_displayAsync:(BOOL)async {__strong id<YYTextAsyncLayerDelegate> delegate = (id)self.delegate;YYTextAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];if (async) {if (task.willDisplay) task.willDisplay(self);_YYTextSentinel *sentinel = _sentinel;int32_t value = sentinel.value;BOOL (^isCancelled)() = ^BOOL() {return value != sentinel.value;};dispatch_async(YYTextAsyncLayerGetDisplayQueue(), ^{if (isCancelled()) {CGColorRelease(backgroundColor);return;}UIGraphicsBeginImageContextWithOptions(size, opaque, scale);CGContextRef context = UIGraphicsGetCurrentContext();if (opaque && context) {CGContextSaveGState(context); {if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));CGContextFillPath(context);}if (backgroundColor) {CGContextSetFillColorWithColor(context, backgroundColor);CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));CGContextFillPath(context);}} CGContextRestoreGState(context);CGColorRelease(backgroundColor);}task.display(context, size, isCancelled);if (isCancelled()) {UIGraphicsEndImageContext();dispatch_async(dispatch_get_main_queue(), ^{if (task.didDisplay) task.didDisplay(self, NO);});return;}UIImage *image = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();if (isCancelled()) {dispatch_async(dispatch_get_main_queue(), ^{if (task.didDisplay) task.didDisplay(self, NO);});return;}dispatch_async(dispatch_get_main_queue(), ^{if (isCancelled()) {if (task.didDisplay) task.didDisplay(self, NO);} else {self.contents = (__bridge id)(image.CGImage);if (task.didDisplay) task.didDisplay(self, YES);}});});} else {[_sentinel increase];if (task.willDisplay) task.willDisplay(self);UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, self.contentsScale);CGContextRef context = UIGraphicsGetCurrentContext();if (self.opaque && context) {CGSize size = self.bounds.size;size.width *= self.contentsScale;size.height *= self.contentsScale;CGContextSaveGState(context); {if (!self.backgroundColor || CGColorGetAlpha(self.backgroundColor) < 1) {CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));CGContextFillPath(context);}if (self.backgroundColor) {CGContextSetFillColorWithColor(context, self.backgroundColor);CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));CGContextFillPath(context);}} CGContextRestoreGState(context);}task.display(context, self.bounds.size, ^{return NO;});UIImage *image = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();self.contents = (__bridge id)(image.CGImage);if (task.didDisplay) task.didDisplay(self, YES);}
}

这里会执行YYTextAsyncLayer的Delegate方法,YYTextAsyncLayer的Delegate就是YYLabel,执行newAsyncDisplayTask,首先明白YYTextAsyncLayer是重绘的时候拦截display方法,在这里能获取到绘图上下文,把上下文代理给YYLabel,然后YYLabel根据是否更新之前排版好的YYTextLayout,调用drawInContext根据是否异步进行对应线程的绘制,这个时候context就已经被绘制好了,然后YYAsyncLayer就会从上下文中拿出对应的Image,在主线程显示到对应的Layer,而且最终的显示都是通过contents来显示上下文中渲染出来的图片,所以这里先代理给YYLabel,会创建一个task并返回,这里会按顺序执行包含三段Block,

willDisplaydisplaydidDisplay
给Layer显示过程中调用
核心绘制过程会执行displayBlock,传上下文和其他参数进行渲染

(YYTextAsyncLayerDisplayTask )newAsyncDisplayTask {
// 1
YYAsyncLayerDisplayTask task = [YYAsyncLayerDisplayTask new];
// 2
task.willDisplay = ^(CALayer *layer) {
// ...
}// 3
task.display = ^(CGContextRef context, CGSize size, BOOL (^isCancelled)(void)) {
// ...
}
// 4
task.didDisplay = ^(CALayer *layer, BOOL finished) {// ...}return task;}
[drawLayout drawInContext:context size:size point:point view:nil layer:nil debug:debug cancel:isCancelled];

通过YYLabel执行的代理方法渲染之后,最后在YYTextAsyncLayer中执行self.contents = (__bridge id)(image.CGImage);获取绘制好的内容

上面的代码if下面就是异步绘制,else表示的就是普通的主线程绘制,如果任务执行久就会有很明显的卡顿

这里有个CancelBlock来取消上一次的任务,避免不必要的异步任务执行,通过OSAtomicIncrement32一个全局递增计数器实现点击打开链接

      

总结:YYLabel调用核心顺序

TableView优化思路:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;

第一个是绘制渲染相关的,第二个是排版计算布局相关的。

https://www.jianshu.com/p/7a353962b3d8这个博主写的很好,借鉴下

优化第一步就是把cell高度也预排版在异步线程计算好,然后缓存成一个个拥有数据源的layout对象。

heightForRowAtIndexPath的时候直接取layout里面的cellHeight,cellForRowAtIndexPath渲染的时候,无非就是Lable和ImageView两个,Label可以把内容渲染在后台线程绘制成一张image,在主线程直接从上下文拿出对应的Image即可。ImageView就是在下载完图片的时候异步解压和处理。

YYTextView

这里的思路和YYText不同,如果你看过AsyncDisplayKit,这就是把一套逻辑拆分成两套渲染的方案
第一个是异步渲染,通过重写触发Display的方法开异步线程进行渲染

第二个是通过Runloop Observe来监听 kCFRunLoopBeforeWaiting | kCFRunLoopExit 这两个状态,而且把优先级设置为系统CA动画之后,在执行完系统动画之后再回到主线程去进行绘制渲染

简单看下实现流程

1.属性调用

    YYTextView *textView = [YYTextView new];textView.attributedText = text;textView.textParser = [YYTextExampleEmailBindingParser new];textView.size = self.view.size;textView.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);textView.delegate = self;if (kiOS7Later) {textView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;}textView.contentInset = UIEdgeInsetsMake(64, 0, 0, 0);textView.scrollIndicatorInsets = textView.contentInset;[self.view addSubview:textView];self.textView = textView;[self.textView becomeFirstResponder];

2.Setter内部标记 _commitUpdate

/// Update layout and selection before runloop sleep/end.
- (void)_commitUpdate {
#if !TARGET_INTERFACE_BUILDER_state.needUpdate = YES;[[YYTextTransaction transactionWithTarget:self selector:@selector(_updateIfNeeded)] commit];
#else[self _update];
#endif
}

外部调用的属性设置都会进行标记,这里的标记和上面的YYText不同,不是setNeedsDisplay,而是把自己设置为回调Target,然后把需要执行的SEL传进去 YYTextTransaction (处理Runloop观察者回调)

+ (YYTextTransaction *)transactionWithTarget:(id)target selector:(SEL)selector{if (!target || !selector) return nil;YYTextTransaction *t = [YYTextTransaction new];t.target = target;t.selector = selector;return t;
}

上面的类方法先设置好Target和SEL

static NSMutableSet *transactionSet = nil;static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {if (transactionSet.count == 0) return;NSSet *currentSet = transactionSet;transactionSet = [NSMutableSet new];[currentSet enumerateObjectsUsingBlock:^(YYTextTransaction *transaction, BOOL *stop) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"[transaction.target performSelector:transaction.selector];
#pragma clang diagnostic pop}];
}static void YYTextTransactionSetup() {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{transactionSet = [NSMutableSet new];CFRunLoopRef runloop = CFRunLoopGetMain();CFRunLoopObserverRef observer;observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),kCFRunLoopBeforeWaiting | kCFRunLoopExit,true,      // repeat0xFFFFFF,  // after CATransaction(2000000)YYRunLoopObserverCallBack, NULL);CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);CFRelease(observer);});
}

然后通过commit 绑定Runloop的两个状态,在主线程中触发YYRunLoopObserverCallBack 方法,然后让Target执行SEL,也就是最终的方法,如下

/// Update layout and selection view immediately.
- (void)_update {_state.needUpdate = NO;[self _updateLayout];[self _updateSelectionView];
}

之后的排版和渲染就会在YYTextContainerView里面调用DrawRect进行绘制,但是这个是主线程的。

CPU资源消耗原因

对象创建 (对象的创建会分配内存、调整属性、甚至还有读取文件等操作,比较消耗 CPU 资源。尽量用轻量的对象代替重量的对象,可以对性能有所优化)

对象调整,(当视图层次调整时,UIView、CALayer 之间会出现很多方法调用与通知,所以在优化性能时,应该尽量避免调整视图层次、添加和移除视图。)

对象销毁,(对象的销毁虽然消耗资源不多,但累积起来也是不容忽视的。)

布局,(视图布局的计算是 App 中最为常见的消耗 CPU 资源的地方。如果能在后台线程提前计算好视图布局、并且对视图布局进行缓存,那么这个地方基本就不会产生性能问题了)

Autolayout,(Autolayout 是苹果本身提倡的技术,在大部分情况下也能很好的提升开发效率,但是 Autolayout 对于复杂视图来说常常会产生严重的性能问题)

文本计算,(如果一个界面中包含大量文本(比如微博微信朋友圈等),文本的宽高计算会占用很大一部分资源,并且不可避免)

文本渲染,(屏幕上能看到的所有文本内容控件,包括 UIWebView,在底层都是通过 CoreText 排版、绘制为 Bitmap 显示的。常见的文本控件 (UILabel、UITextView 等),其排版和绘制都是在主线程进行的,当显示大量文本时,CPU 的压力会非常大。对此解决方案只有一个,那就是自定义文本控件,用 TextKit 或最底层的 CoreText 对文本异步绘制。尽管这实现起来非常麻烦,但其带来的优势也非常大,CoreText 对象创建好后,能直接获取文本的宽高等信息,避免了多次计算(调整 UILabel 大小时算一遍、UILabel 绘制时内部再算一遍);CoreText 对象占用内存较少,可以缓存下来以备稍后多次渲染。) 这个其实就是YYText的核心

图片解码,(当你用 UIImage 或 CGImageSource 的那几个方法创建图片时,图片数据并不会立刻解码。图片设置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 GPU 前,CGImage 中的数据才会得到解码。这一步是发生在主线程的,并且不可避免。如果想要绕开这个机制,常见的做法是在后台线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片。目前常见的网络图片库都自带这个功能。)

图像绘制等操作图像的绘制通常是指用那些以 CG 开头的方法把图像绘制到画布中,然后从画布创建图片并显示这样一个过程。这个最常见的地方就是 [UIView drawRect:] 里面了。由于 CoreGraphic 方法通常都是线程安全的,所以图像的绘制可以很容易的放到后台线程进行。)

GPU资源消耗原因

纹理渲染 (所有的 Bitmap,包括图片、文本、栅格化的内容,最终都要由内存提交到显存,绑定为 GPU Texture)

视图混合    (当多个视图(或者说 CALayer)重叠在一起显示时,GPU 会首先把他们混合到一起。)

图形生成 (CALayer 的 border、圆角、阴影、遮罩(mask),CASharpLayer 的矢量图形显示,通常会触发离屏渲染(offscreen rendering),而离屏渲染通常发生在 GPU 中)

上面两条都摘抄自YY博客

按照上面的原因和思路,YYText和YYTextView就处理了这些点,主要包括两个点

1.自定义View 把layerClass指向自定义Layer,然后在Layer上重写Display方法(通过setNeedsDisplay标记触发),自定义实现异步渲染,或者一开始把数据源直接异步排版,再通过渲染的时候进行异步绘制渲染
2.通过属性的设值把任务提交到 YYTextTransaction 类中的全局Set容器存储对象,然后在该类实现Runloop观察,通过观察kCFRunLoopBeforeWaiting | kCFRunLoopExit 两个方法,优先级低于系统动画,然后把排版和渲染的耗时操作在最后执行,保证用户事件和系统动画的流畅,把耗时操作放到Runloop即将进入休眠之前处理,这里不涉及到滑动,因此主线程绘制,把优先级低于Core Animation就能保证用户和系统优先。

AsyncDisplayKit

这个才是终极的UI页面优化方案的集合体,很多思路都源自于这里

ASDK 对于绘制过程的优化有三部分:分别是栅格化子视图、绘制图像以及绘制文字。

它拦截了视图加入层级时发出的通知 - willMoveToWindow: 方法,然后手动调用 - setNeedsDisplay,强制所有的 CALayer 执行 - display 更新内容;

然后将上面的操作全部抛入了后台的并发线程中,并在 Runloop 中注册回调,在每次 Runloop 结束时,对已经完成的事务进行 - commit,以图片的形式直接传回对应的 layer.content 中,完成对内容的更新。

这里ASDK的做法是,触发绘制任务直接提交到后台并发队列里面,然后给一个观察者监听Runloop BeforeWaiting,根据key去队列里面拿会知道的image,如果有就返回主线程显示到layer.content中,但是YYTextView只是一个简单的主线程思路而已,他是把任务提交到set里面,在观察者Runloop即将休眠的时候拿出来在最后的优先级里面进行主线程绘制。

从它的实现来看,确实解决了很多昂贵的 CPU 以及 GPU 操作,有效地加快了视图的绘制和渲染,保证了主线程的流畅执行。

YY的作者也说从中学到了不少知识,他的优化实现方案可以理解为一个更容易理解的版本,有兴趣的可以看看ASDK的源码,这里有个大神介绍的很详细了

参考文章

灯神

YYkit

CPU 和 GPU

简书介绍

OSAtomicIncrement32

YYText

流程方法介绍

iOS tableView性能优化之异步排版和绘制渲染----YYText框架学习(YYTextAsyncLayer)相关推荐

  1. iOS之性能优化·列表异步绘制

    一.前言 iOS 所提供的 UIKit 框架,其工作基本是在主线程上进行,界面绘制.用户输入响应交互等.当大量且频繁的绘制任务,以及各种业务逻辑同时放在主线程上完成时,便有可能造成界面卡顿.丢帧现象, ...

  2. iOS tableview性能优化及分析

    1.最常用的就是cell的重用, 注册重用标识符 每次滑动cell时需要先去缓存池中寻找可循环利用的cell,如果没有则再重新创建cell 2.减少cell中控件的数量 view对象尽量缩减控件的数量 ...

  3. 性能优化与压测引擎一:压测框架gatling

    性能优化与压测引擎一:压测框架gatling 压测框架gatling 简单使用介绍 压测框架gatling 说到压测,我们最熟悉的可能就是ab命令和jmeter了,确实他们很方便,但是我最近都在用另外 ...

  4. iOS之性能优化·优化App界面的渲染与流畅度

    一.界面渲染流程 ① 渲染流程分析 计算机中的显示过程通常是通过 CPU.GPU.显示器协同工作来将图片显示到屏幕上,如下图所示: 苹果为了解决图片撕裂的问题使用了 VSync + 双缓冲区的形式,就 ...

  5. ios 常见性能优化

    1. 用ARC管理内存 2. 在正确的地方使用reuseIdentifier 3. 尽可能使Views透明 4. 避免庞大的XIB 5. 不要block主线程 6. 在Image Views中调整图片 ...

  6. iOS app性能优化的那些事

     iPhone上面的应用一直都是以流畅的操作体验而著称,但是由于之前开发人员把注意力更多的放在开发功能上面,比较少去考虑性能的问题,可能这其中涉及到objective-c,c++跟lua,优化起来相对 ...

  7. iOS app性能优化

    instruments   在iOS上进行性能分析的时候,首先考虑借助instruments这个利器分析出问题出在哪,不要凭空想象,不然你可能把精力花在了1%的问题上,最后发现其实啥都没优化,比如要查 ...

  8. 网页性能优化之异步加载js文件

    一个网页的有很多地方可以进行性能优化,比较常见的一种方式就是异步加载js脚本文件.在谈异步加载之前,先来看看浏览器加载js文件的原理. 浏览器加载 JavaScript 脚本,主要通过<scri ...

  9. 《深入浅出DPDK》读书笔记(八):网卡性能优化(异步中断模式、轮询模式、混和中断轮询模式)

    本文内容为读书笔记,摘自<深入浅出DPDK> 82.DPDK的轮询模式 <DPDK PMD( Poll Mode Driver)轮询模式驱动程序> DPDK采用了轮询或者轮询混 ...

最新文章

  1. java.sql.SQLException: Lock wait timeout exceeded
  2. 社团部部长工作计划计算机学院,社团部部长工作计划(共8篇).doc
  3. SpringBoot+AntV实现饼状图中的花瓣图
  4. php soecket服务器搭建_PHP socket 服务器框架集
  5. 多数据源处理-苞米豆-dynamic
  6. asp.net ajax实现在线人员的显示
  7. 复杂度及圈复杂介绍介绍
  8. Python Re 模块超全解读
  9. php留言板系统制作,php制作留言板讲解
  10. java实现阿里云短信验证
  11. mysql myisam转innodb_转:MySQL从MyISAM引擎转换到InnoDB引擎需要注意的地方
  12. Hive ,Hsql行转列、列转行实现
  13. 花开不败——复旦中文系女生的高三笔记
  14. [2002] PDOException in Connection.php line 295
  15. Facebook架构解读
  16. 如何在 Virtual Box 上下载 Docker
  17. 中国网络优化行业发展前景预测分析及投资风险评估报告2021-2027年
  18. ERP解决方案选型指导
  19. 从Vivado启动ModelSim时遇到的问题([USF-modelsim-8] Failed to find the pre-compiled simulation library!)
  20. Windows10 部署Davinci开发环境

热门文章

  1. 复盘:我的三个月远程办公实践,有自由,也有代价
  2. TCN(Temporal Convolutional Network,时间卷积网络)
  3. python一维数组使用方法_python中向一维数组添加元素的方法
  4. 电大本科计算机基础及应用机考,最新国家开放大学电大本科网考机考形考《计算机应用基础》单选题题库完整版...
  5. 护眼台灯色温多少对眼睛好?2023新年学生台灯最合适的色温推荐
  6. 浏览器下载安装vue插件
  7. 为全面助力青少年编程教育普及,推出花瓣少儿编程
  8. Python毕业设计基于django的同城绘本馆系统-绘本图书商城(源码+系统+mysql数据库+Lw文档)
  9. Java基于springboot大学生宿舍寝室考勤人脸识别管理系统
  10. Android-使用Jenkins自动化打包详解-Linux篇