drawRect导致内存暴增的真正原因
那么现在我们分析一下drawRect
导致内存暴增的真正原因:
重写drawRect
为何会导致内存大量上涨?
要想搞明白这个问题,我们需要撸一撸在 iOS 程序上图形显示的原理。在 iOS 系统中所有显示的视图都是从基类UIView
继承而来的,同时UIView
负责接收用户交互。 但是实际上你所看到的视图内容,包括图形等,都是由UIView
的一个实例图层属性来绘制和渲染的,那就是CALayer
。
CALayer
类的概念与UIView
非常类似,它也具有树形的层级关系,并且可以包含图片文本、背景色等。它与UIView
最大的不同在于它不能响应用户交互,可以说它根本就不知道响应链的存在,它的 API 虽然提供了 “某点是否在图层范围内的方法”,但是它并不具有响应的能力。
在每一个UIView
实例当中,都有一个默认的支持图层,UIView
负责创建并且管理这个图层。实际上 这个CALayer
图层才是真正用来在屏幕上显示的 ,UIView
仅仅是对它的一层封装,实现了CALayer
的delegate
,提供了处理事件交互的具体功能,还有动画底层方法的高级 API。
可以说CALayer
是UIView
的内部实现细节。
脑补了这么多,它与今天的主题drawRect
有何关系呢?别着急,我们既然已经确定CALayer
才是最终显示到屏幕上的,只要顺藤摸瓜,即可分析清楚。CALayer
其实也只是 iOS 当中一个普通的类,它也并不能直接渲染到屏幕上,因为屏幕上你所看到的东西,其实都是一张张图片。而为什么我们能看到CALayer
的内容呢,是因为CALayer
内部有一个contents
属性。contents
默认可以传一个id
类型的对象,但是只有你传CGImage
的时候,它才能够正常显示在屏幕上。 所以最终我们的图形渲染落点落在contents
身上 如图。
contents
也被称为寄宿图,除了给它赋值CGImage
之外,我们也可以直接对它进行绘制,绘制的方法正是这次问题的关键,通过继承UIView
并实现-drawRect:
方法即可自定义绘制。-drawRect:
方法没有默认的实现,因为对UIView
来说,寄宿图并不是必须的,UIView
不关心绘制的内容。如果UIView
检测到-drawRect:
方法被调用了,它就会为视图分配一个寄宿图,这个寄宿图的像素尺寸等于视图大小乘以contentsScale
(这个属性与屏幕分辨率有关,我们的画板程序在不同模拟器下呈现的内存用量不同也是因为它) 的值。
那么回到我们的画板程序,当画板从屏幕上出现的时候,因为重写了-drawRect:
方法,-drawRect :
方法就会自动调用。 生成一张寄宿图 后,方法里面的代码利用Core Graphics
去绘制 n 条黑色的线,然后内容就会缓存起来,等待下次你调用-setNeedsDisplay
时再进行更新。
画板视图的-drawRect:
方法的背后实际上都是底层的CALayer
进行了重绘和保存中间产生的图片,CALayer
的delegate
属性默认实现了CALayerDelegate
协议,当它需要内容信息的时候会调用协议中的方法来拿。当画板视图重绘时,因为它的支持图层CALayer
的代理就是画板视图本身,所以支持图层会请求画板视图给它一个寄宿图来显示,它此刻会调用:
- (void)displayLayer:(CALayer *)layer;
如果画板视图实现了这个方法,就可以拿到layer
来直接设置contents
寄宿图,如果这个方法没有实现,支持图层CALayer
会尝试调用:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
这个方法调用之前,CALayer
创建了一个合适尺寸的空寄宿图(尺寸由bounds
和contentsScale
决定)和一个Core Graphics
的绘制上下文环境,为绘制寄宿图做准备,它作为ctx
参数传入。在这一步生成的空寄宿图内存是相当巨大的,它就是本次内存问题的关键,一旦你实现了CALayerDelegate
协议中的-drawLayer:inContext:
方法或者UIView
中的-drawRect:
方法(其实就是前者的包装方法),图层就创建了一个绘制上下文,这个上下文需要的内存可从这个公式得出:图层宽
*图层高
*4 字节
,宽高的单位均为像素。而我们的画板程序因为要支持像猿题库一样两指挪动的效果,我们开辟的画板大小为:
_myDrawer = [[BHBMyDrawer alloc] initWithFrame:
CGRectMake(0, 0, SCREEN_SIZE.width*5, SCREEN_SIZE.height*2)];
我们的画板程序的画板视图它在iPhone6s plus
机器上的上下文内存量就是 1920*2
*1080*5
*4 字节
, 相当于79MB
内存 ,图层每次重绘的时候都需要重新抹掉内存然后重新分配。它就是我们画板程序内存暴增的真正原因。
最终我们将内存暴增的原因找出来了,那么我们有没有合理的解决方案呢?
我认为最合理的办法处理类似于画板这样画线条的需求直接用专有图层CAShapeLayer
。让我们看看它是什么:
CAShapeLayer
是一个通过矢量图形而不是bitmap
来绘制的图层子类。用CGPath
来定义想要绘制的图形,CAShapeLayer
会自动渲染。它可以完美替代我们的直接使用Core Graphics
绘制layer
,对比之下使用CAShapeLayer
有以下优点:
渲染快速。CAShapeLayer 使用了硬件加速,绘制同一图形会比用 Core Graphics 快很多。
高效使用内存。一个 CAShapeLayer 不需要像普通 CALayer 一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。
不会被图层边界剪裁掉。
不会出现像素化。
所以最终我们的画板程序使用CAShapeLayer
来实现线条的绘制,性能非常稳定,效果图如下:
http://mp.weixin.qq.com/s?__biz=MjM5NTIyNTUyMQ==&mid=447105405&idx=1&sn=054dc54289a98e8a39f2b9386f4f620e&scene=0#wechat_redirect
drawRect导致内存暴增的真正原因的更多相关文章
- (转)使用SDWebImage和YYImage下载高分辨率图,导致内存暴增的解决办法
http://blog.csdn.net/guojiezhi/article/details/52033796
- httpd2.4.6三种工作模式(如何配置),防止占用内存暴增的策略
之前偷懒默认用yum安装了httpd.后来发现服务器内存暴增,一度达到75% 打开一看,好嘛后台休眠进程全是httpd. 重启之后再度访问发现内存还是稳步增长. [root@iz2ze3ayxs2yp ...
- BitArray虽好,但请不要滥用,又一次线上内存暴增排查
一:背景 1. 讲故事 前天写了一篇大内存排查在园子里挺火,这是做自媒体最开心的事拉,干脆再来一篇满足大家胃口,上个月我写了一篇博客提到过使用bitmap对原来的List<CustomerID& ...
- 解決 imagick 在 多线程运行时导致CPU暴增到100%的方法
假如把imagic 安装到 /usr/local/imagemagick 目录 首先用/usr/local/imagemagick/bin/convert -version指令查看一下输出內容是否已经 ...
- xcode设置 - App内存暴增
当你发现你的项目中什么没有写,只是启动App内存就飙升到50M甚至60M以上,那么请你接着往下看吧,本文对你绝对非常有用! 1. Enable zombie object: 为了方便我们调试程序,我们 ...
- iOS - drawRect致内存增加
GPU VS CPU iOS - 软件绘图 自定义"斑马线背景"View,重写drawRect绘制斑马线: ⚠️ 仅仅添加这一个View,内存就比正常增加了3-5M之间. 测试源代 ...
- 添加IFrame导致内存溢出的解决过程(IE浏览器,目前发现了原因,还未解决)
1. 现象 每次动态添加iframe时,iexplore.exe进程占据的内存都会增加(大概10M左右),不会自动释放,最终导致内存溢出 2. 解决过程 经过网络的一番搜索,基本上给出的解决方案是 ...
- Android 非静态内部类导致内存泄漏原因深入剖析
背景 上周发现蘑菇街IM-Android代码里面.一些地方代码编写不当.存在内存泄漏的问题.在和疯紫交流的过程中.发现加深了一些理解,所以决定写一下分析思路,相互学习. 内存泄漏 一个不会被使用的对象 ...
- Android 内存暴减的秘密?!
作者:杨超,腾讯移动客户端开发 工程师 商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处. WeTest 导读 在我这样减少了26.5M Java内存! 一文中内存优化一期已经告一段落, ...
随机推荐
- Windows7 IIS7.5 HTTP Error 503 The service is unavailable 另类解决方案
这篇文章是在你看了别的解决方案仍然解决不了之后才有用. 所以再未用别的解决方案之前,用了该解决方案依然无效的话,请自己看着办. 原创: .net2.0和.net3.5的应用程序池请在开始菜单打开VS2 ...
- sql except 用法,找两个表中非共同拥有的
;with tt as (select a.id as id from [dbo].[1234] a where a.id not in (select a.ProtocolID from Proto ...
- hql语句查询实体类采用list方法的返回结果集
在hibernate中,用hql语句查询实体类,采用list方法的返回结果为一个List,该List中封装的对象分为以下三种情况:1.查询全部字段的情况下,如"from 实体类", ...
- windows下快速启动 nginx 和 php-cgi 的两个批处理
这是启动的批处理: set nginx=D:\nginx-1.9.5\ set php=D:\php\ start /MIN %nginx%nginx.exe start /MIN %php%php- ...
- 【RoR win32】安装RoR
在配置好ruby的win 7 命令行下运行, gem install rails 安装成功之后会收到提示,下面就可以用rails建立项目了. 为了提高“rails new”时“bundle insta ...
- php curl 抓去远程页面内容
<?php/*** php curl抓取远程网页内容* edit by www.jbxue.com*/$curlPost = 'a=1&b=2';//模拟POST数据$ch = curl ...
- Openstack的HA解决方案【haproxy和keepalived】
1. 安装haproxy,keepalived, httpd,3台机器一致. yum install haproxy keepalived httpd -y 2. 修改httpd的默认页面. 在/va ...
- OpenStack 界面开发中的排序问题
Contents [hide] 1 需求 2 调研 3 排序的办法 4 解决代码 需求 获取主机列表的时候,希望能够对主机列表能分组显示,比如网络,一组网络段希望在一起显示 调研 openstack的 ...
- java IO复习笔记
1. IO是什么? Input Output Java的核心库java.io提供了全面的IO接口.包括:文件读写.标准设备输出等.Java中IO是以流为基础进行输入输出的,所有数据被串行化写入输出流, ...
- TI BLE CC2541的通讯协议.
包类型: 01命令/02数据/03应答消息 开始标志FF/本数据包长度(注意是16进制)/校验码/包ID/包类型01: 表示是命令/01表示下面要开始传输/03字符串编号/字符串长度/结束位FEFF ...