CATiledLayer参数解释/踩坑

CATiledLayer是什么

CATiledLayer顾名思义,把绘制区域分成小块,采用类似贴瓷砖的方式进行绘制。

想象一张50000*50000大图,分解成一个个100*100小块,每次绘制,只需要绘制屏幕内的部分,从而达到省内存的目的。

它很适合与UIScrollView一起,实现超大图的手势放大缩小。网上关于CATiledLayer的demo有很多,但是都没说清其中的参数是什么意思。这里就不贴代码了,主要针对参数做一下解释。

CATiledLayer只暴露一个函数+三个变量,如图所示,下面对其一一进行解释。

什么是tileSize

tileSize就是分片大小。以像素为单位。

理解这一点,十分十分重要。

例如,你的手机是3倍屏,tileSize设为屏幕宽度(pt)的正方形,你会看到3x3个tile。因为每一个tile实际上是屏幕宽度的三分之一。

实际项目中,设置为屏幕宽度的正方形,感觉就没啥毛病。设置的太小,会导致tile太多,影响绘制效率。

什么是levelsOfDetail、levelsOfDetailBias

CATiledLayer不是单层瓷砖。

每一层瓷砖,代表一层清晰度水平。放大后,会使用下一层瓷砖显示。

比如,考虑tileSize设置为100*100,CATiledLayer所在UIView大小也是100*100,手机是一倍屏,展示一张10000*10000的图片。

第一层,只有一个瓷砖,把10000*10000的图采样成100*100显示。第二层,有2*2=4个瓷砖,10000*10000采样成200*200显示。以此类推,第三层4*4=16个瓷砖,400*400...

每一层,比上一层多2x2=4倍的瓷砖。显然,越往下放大,采样的图片大小越大,显示在屏幕上的内容越清晰。

有的朋友会说,你这放大以后,采样的像素也大了啊!放大到很大时,岂不是还是费很大内存,和使用普通的CALayer没区别啊???

非也。

你放大以后,屏幕中显示的瓷砖数也少了。CATiledLayer只会绘制屏幕中正在展示的瓷砖。你永远不可能把第三层16个瓷砖、第四层64个瓷砖完全显示在屏幕。这就是CATiledLayer既省内存又能显示清晰的根本原因!

levelsOfDetail就代表有几层瓷砖。levelsOfDetailBias代表有几层瓷砖是用来放大的。一般我们设置levelsOfDetailBias = levelsOfDetail - 1

在上面的例子中,我们设置levelsOfDetail = 5,levelsOfDetailBias = 4,则有1x1、2x2、4x4、8x8、16x16,五层瓷砖。若levelsOfDetailBias也等于5,则没有1x1,但多了32x32。

瓷砖以2次幂放大倍数为阈值进行切换。比如,UIScrollView放大倍率大于1了,则开始显示2x2层,大于2了,显示4x4层,大于4了,显示8x8层。

什么是fadeDuration

默认值是0.25,代表放大时,瓷砖渐变出现的时长,以秒为单位。

一般都希望设置为0,因为这个渐变效果会显得app运行缓慢。

CATiledLayer绘制流程推测

一般采用CATiledLayer的UIView这样定义

class CBTiledLayer: CATiledLayer{
    
    override class func fadeDuration() -> CFTimeInterval {
        0
    }
    
}


class CBLineLayerView: UIView{
    
    override class var layerClass: AnyClass {
        CBTiledLayer.self
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
   
    weak var tiledLayer: CATiledLayer?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        tiledLayer = (self.layer as! CATiledLayer)
        // 这里设置levelsOfDetail
    }
    
    override func draw(_ rect: CGRect) {
        // 这里进行绘制操作
    }
    
}

所有的绘制操作,都写在draw中。

经实测发现:

1.draw并不是画整个UIView,而是只画一个瓷砖。

2.并不是每次拖动、放大,都会触发draw。

3.draw的内容永远不能超过rect的范围。

4.绘制过的rect,后面可能还会重新触发draw。

5.这个函数永远在子线程中调用,哪怕设置了drawAsyncroniously=false。

推测,draw是一个准备瓷砖的接口。哪个位置的瓷砖,该显示什么内容,通过这个接口定义。它绘制出来的内容,并不直接显示在屏幕上,而是放在CoreAnimation的缓存里。当View需要用到某一片瓷砖的时候,便从缓存中拿出来绘制到屏幕上。当缓存满了,系统会销毁之前的瓷砖,以容纳新的瓷砖。

CATiledLayer的使用者,只需要定义每个瓷砖要显示什么内容。具体该显示哪个瓷砖,该绘制哪个瓷砖,该回收哪个瓷砖,由系统管理,不需要使用者关心。

永远不要使用drawAsyncroniously

因为CATiledLayer本身就是在后台线程准备瓷砖,设置drawAsyncroniously = true,会产生线程同步问题,从而发生闪屏。

使用CATiledLayer时,永远不要在后台线程创建任何CALayer

当CATiledLayer存在时,只要我们在后台线程创建其他CALayer(仅仅是创建,都不需要加入到View中),就会触发CATiledLayer的bug。

具体表现为,CATiledLayer无视缓存的存在,疯狂重绘屏幕区域的瓷砖,从而产生OOM。

看内存,CATiledLayer发生内存泄漏。哪怕CATiledLayer所在的VC已经pop掉了,CATiledLayer所在的UIView都被回收掉了,CATiledLayer及其缓存所占用的内存都无法被释放。根引用来自一个Stack,推测是某个线程的函数调用栈。

猜测,后台线程创建CALayer后,打乱了CoreAnimation本身的节奏,使得某些绘制CATiledLayer的线程被CoreAnimation忽视掉了。即使这些线程绘制好了瓷砖,CoreAnimation也不理不睬,晾着它们,既不使用其绘制的瓷砖,也不把线程结束掉,从而引发线程和瓷砖图片的泄漏。

强制重刷CATiledLayer

有时,CATiledLayer中的内容,是需要等待加载完成的。

当CATiledLayer所在的UIView对用户可见时,系统就会调用draw函数,但因为内容还没有准备好,会显示空白。

当图片准备好后,调用UIView的setNeedsDisplay,经实测,可能并不能完全刷新CATiledLayer的显示。这时就需要用到一个强制刷新CATiledLayer的trick。

layer.contents = nil
layer.setNeedsDisplay(layer.bounds)

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值