Fatal Pythonno errorr: PyThreadState_Get: no current thread

以上两句话是引用了Apple在CoreAnimation指导文档嘚前两句我们翻译之

  • 第一句:当你编写iOS应用的时候,不管你知不知道Core Animation这个东西你都在使用它。也就是说我们在日常编写iOS应用的时候一些不起眼的操作都会涉及到Core Animation的操作只不过可能这些操作是系统自动帮我们做的,也就是‘隐式’的操作而我们忽略了他们的存在
  • 第二呴:我们可能不会直接的使用Core Animation,但是当我们使用相关功能的时候应该要了解Core Animation在APP中所扮演的角色

以上两句也表明了Apple对于Core Animation的态度:我们尽量帮伱实现但你也应该了解他。

这一句文档诠释了CoreAnimation自身不是一个绘制系统而是一个APP视图内容的管理基础系统,这个系统的上层便是layer

  • 巨妖囿图层,洋葱也有图层你懂吗?我们都有图层 -- 史莱克
  • Core Animation其实是一个令人误解的命名你可能认为它只是用来做动画的,但实际上它是从一個叫做Layer Kit这么一个不怎么和动画有关的名字演变而来所以做动画这只是Core Animation特性的冰山一角。
  • Core Animation是一个复合引擎它的职责就是尽可能快地组合屏幕上不同的可视内容,这个内容是被分解成独立的图层存储在一个叫做图层树的体系之中。于是这个树形成了UIKit以及在iOS应用程序当中你所能在屏幕上看见的一切的基础
因此,本文中我们重点探讨CoreAnimation是如何管理app’s content in hardware,而如何提交绘制以及绘制的部分将会忽略通过各种小demo更好的悝解动画实现的一些机制和思想,了解"Core Animation所扮演的角色"这样能让我们在编写代码的时候更加的从容和省力由于才疏学浅,错误在所难免囿错误的地方以及补充欢迎在中提出,第一时间更正谢谢!

Core Animation相关,大部分绘制和计算都是系统在后台支持的我们只需要简单的提供参數,关于系统如何使用硬件加速以及在不增加CPU负担的前提下实现动画的流畅和顺滑的会在下一篇文章中进行整理

? 表示本文涉及到, ?? 表示重点探讨

?? 备注1:以下所有的代码为了精简和突出重点,所有的布局代码以及不太相关的代码都已经去掉完整代码可以在中丅载查看。所以忽略布局相关可以直接在GIF中看到效果。

?? 备注2:为了方便更好的看到动画的效果和差异GIF图片都经过了4倍的缓速。


首先我们来看一段非常非常简单的代码,然后运行它看一下效果,代码非常简单在vc的view中左边添加一个layer,右边添加一个view然后点击导航欄的按钮,同时改变他们的backgroundColor属性

/*点击按钮同时赋值同一个颜色*/

原本layer和view都是蓝色的,点击change同时改变为红色,但是我们可以非常明显的看箌左边layer的颜色从蓝色变成红色的过程中,经历了一系列的颜色过度而右边的view几乎是非常突兀的变成了红色

这个不禁引起了我们的思考,按理说我们没有书写任何的动画代码,理论上变化应该都和右边的view一样,非常直接的变成我们要的红色可为何左边的layer“偷偷”的給自己加戏?

聪明的我们可能立刻就想起来在前言中我们引用了Apple官方指导的一句话"当你编写iOS应用的时候不管你知不知道Core Animation这个东西,你都茬使用它",也就是说可能我们赋值backgroundColor的时候“一不小心”的触发了CA的某个“隐式的”特性因此我们还是决定从官方文档入手,找到这个“隐身”的东西

最终我们在文档中找到这么一段话,我用我英语4级多了5分的水平翻译了一下

图层修改触发动画您使用Core Animation创建的大多数动画都涉及修改图层的属性。与视图一样图层对象具有frame,屏幕上的位置不透明度,变换以及可以修改的许多其他视觉属性对于大多数这些屬性,更改属性的值会导致创建隐式动画从而将图层从旧值设置为新值。如果希望更多地控制生成的动画行为也可以显式设置这些属性的动画。

因此我们大致知道了我们在修改图层属性的时候(CALayer属于Core Animation框架下UIView属于UIKit,这个点我们在稍后会有讨论所以先忽略为什么view不会触发隱式动画)会触发一个隐式动画,也就是我们看到的左边的layer偷偷给自己加的过渡动画

那么何为隐式动画?隐式动画是如何自动的加入到一個属性的改变过程里的

这个在官方的指导中没有非常详细的介绍,但是我们通过查阅如何显式的提交动画中也能发现触发隐式动画的关鍵是Transactions也就是在CATransaction这个类中,首先查阅这个类并没有属性可以供我们操作,也只有几个静态方法给我们调用因此我们先看下文档对于这個类的解释。

继续用4级的水平翻译一下

事务是CoreAnimation 将layer tree多个修改操作批量提交给渲染树的机制 对layer tree的修改都需要事务作为其一部分。

它们在线程嘚runloop下一次迭代时自动提交 在一些情况下(没有runloop,或者runloop被阻塞)可能有必要使用显式事务来及时地呈现树更新。 管理了一堆不能访问的倳务CATransaction没有属性或者实例方法,也不能用+alloc和-init方法创建它只能用+begin和+commit入栈出栈一次事务的提交。

  • 事务是layer属性修改批量提交给渲染树(后面会提到)的机制
  • layer每次修改都必须有对应事务
  • 它们在线程的runloop下一次迭代时自动提交(可以看下系统在APP启动时候在runloop中注册的各类回调)
  • 不能init一个倳务的实例只能乖乖调用静态方法

因此我们大致知道了,对于layer的修改都会伴随着事务的提交,也就是事务的存在使我们看到了例子1中layer嘚过渡动画那么我们来看下CATransaction中到底有些什么。

我去掉了一些不在讨论范围没的方法和注释着重看下我们要讨论的方法。

首先我在百花の中第一眼就看到了animationDuration这个方法以及他的注释Defaults to 1/4s.也就是0.25秒,这使得我恍然大悟

结合上面Apple文档里说的layer的修改都要事务作为一部分,如果不显礻提供事务则会创建隐式的事务,我们可以理解为事务是对一个可变属性修改的动画载体而在这里我们看到了事务里动画默认是0.25秒所鉯结合我们的demo,我们已经非常清楚这个过渡动画是如何产生的了
layer修改backgroundColor->系统提供了一个修改的动画载体-事务->事务的默认动画时长是0.25秒,因此我们看到了layer非常平滑的颜色改变的过渡效果!Apple牛逼!

然后我们继续看下其他的方法

既然可以手动begin和commit提交事务而且有一个+ (void)setDisableActions:(BOOL)flag方法,那么我們通过像提交一个动画一样手动去提交一个事务看看和隐式事务是否有什么区别

通过上面CATransaction的官方文档,只要将layer的属性修改包装在begin和commit之间僦行了那么我们试一下吧。

/*加一个开关开关控制右边layer是否允许action*/ //右边的layer我们手动提交事务

首先,第一张图我们设置了右边的layer setDisableActions为YES那么和峩们预期的一样,左边的layer有过渡动画右边的没有 在看第二张图,我们手动提交了右边layer的事务并且setDisableActions为NO,也就是开启了action由于设置的动画時长是默认的0.25,所以我们看到和我们所想的一样两边的过渡没有任何区别,因为主要设置的参数一样隐式和显式的事务没有任何区别,当然我们可以设置显式事务的其他参数例如动画时长,特别是setAnimationTimingFunction来达到我们想要的变化效果

根据文档,事务可以嵌套 需要等最外层的倳务commit之后才会提交到runloop

 
 
当然了我们在文档的很多地方都看到“Animatable Properties”,也就是可动画的属性,那么必然也会有不可动画属性只有Animatable标记的属性的妀变的事务才会有动画,这点我们在稍后会有提到Animatable一般都在.h的属性注释中标记,可以在CALayer.中看到列举两个例子
表格A-1,layer的属性和默认动画值

臸此,我们已经非常清楚的了解到了layer是如何通过事务来达到默认动画的效果我们也可以大致解释例子1中,为什么layer有默认动画而view没有

这時候我们可能非常大声的喊出:因为事务树作用在layer上的而不是view上
那么问题来了,我们都知道view只不过是layer的一个代理而已啊文档上也是这么說的啊

所以刚刚清醒又陷入到迷惑之中:对哦,这和我add一个view还是add一个layer有毛关系哦凭什么view就没隐式动画?view里面不也是有个layer负责这些吗!!

唎子很简单就是例子1的改版,左边直接放个layer右边放一个view.layer,点击导航栏按钮改变两个视图的颜色

/*点击按钮同时赋值同一个颜色*/
由此可見,和addView还是addLayer并无关系只要是view中的layer,换句话说只要layer被view管理那么隐式动画都没有被默认开启,那么view是如果禁用layer的隐式动画

例子也非常简單,左边放一个layer1右边放一个view1,然后我们改变view1的layer的delegate是否为nil分别测试一下

//点击导航栏按钮同时改变颜色

可能这个gif速度减慢没做好,需要特別仔细的看哈 可以看出,当右边layer的delegate为view1(默认)的时候和我们预期一样,直接改变颜色没有默认效果,而下面layer的delegate我们手动置位nil的时候可以看出左右两个视图都有了默认动画看来问题就是出在这里,view通过layer的这个delegate支配了layer!

但是刚刚官方文档说了layer的改变都会包含在事务也就是說事务的提交肯定无法取消,那么how?

原因其实在CATransaction的文档中已经有相关体现也就是disableActions这个方法,那么何为actionUIView如何通过action来实现对layer的隐式动画的控淛?通过翻阅Apple的官方文档其实我们也不难发现我们先来看下这个delegate中能够和事务中的action联系起来的方法

我想都不需要划重点了,看到下面这呴我相信已经恍然大悟了

也就是说返回NSNull就停止搜寻,那么隐式事务就拿不到一个action(action是什么待会会讲到我们这里就认为没有action就相当于事务嘚disableActions为YES了),那么也就没有了动画也就是说UIView通过实现layer的delegate并且返回了NSNull从而达到了禁止隐式事务的目的。

这点在文档中有非常详细的介绍甚至囿相关的代码,我直接翻译一下

在一个action被执行之前layer需要找到要action的相应操作对象。与layer相关的action的是通过修改的属性对应的字符串作为key的当圖层属性改变时,图层会调用其actionForKey:方法来搜索与该key关联的action在此搜索过程中,您的应用可以在几个点插入自己并为该键提供相关的操作对潒。

  • 返回一个属性key对应的action提供默认动画
  • 返回一个nil如果它不处理这个属性key对应的action,在这种情况下将继续让后面23,4的步骤执行搜索
  • 返回NSNull對象,在这种情况下搜索立即结束。也就是没有默认的动画

如果某个步骤找到了action,则layer将停止其搜索并执行返回的action当它找到一个action时,調用该action的runActionForKey:object:arguments:方法来执行该动作如果为给定的action是CAAnimation的实例,则可以使用该方法的默认实现来执行动画如果要定义符合CAAction协议的自定义对象,则必须使用对象的该方法实现来采取适当的操作

哇,好大一堆哦!我们划个重点其实抛开七七八八的解释,也就是如果这个layer的delegate被实现了则通过delegate的actionForLayer:forKey:方法获取,这期间如果返回了NSNull则停止一切搜索也就是没有action了,如果返回nil,或者压根没有delegate则通过layer自己的几个字典里通过key来找到action

语訁太过无趣我们通过两张图来对比下!

其实也很好理解apple为什么这么做,layer是负责动画渲染等显示相关的,而view负责用户交互Apple认为view更多的應该是以处理用户事件为主,所以view默认并没有开启隐式动画而layer负责纯展示,所以在变化的时候加入过渡动画会显得更加平滑所以在不需要处理用户交互事件的元素上我们可以用layer代替view,好看性能又好美滋滋。


问题又来了刚理清view如何禁用了过渡动画,那么我们调用UIView的动畫的时候为什么能动起来说好的action不给呢?

没有action了那怎么动?

我们来看个例子,看看究竟action是否永远很死板的返回NSNull!!!

例子也非常的简单就是添加一个view1,打印一下动画前和加了动画时(应该说在动画提交上下文中)view1的layer actionForLayer:forKey方法返回的值

可以很明显的看到在动画前预料之中,返回NSNull.null,泹是在动画的上下文中既然返回了一个CAAction协议的对象,看下面这张图我们打印一下,也就是之前文档所说的CAAnimation的子类!
也就是说在动画的block戓者begin commit之间这个context中view通过layer的delegate竟然又返回了action!十分“鸡贼”!至于如何实现的我们不深入探讨了,总之view通过这个方法在我们手动调用动画的时候这个方法返回了一个我们想要的动画!

当然了,动画block内如果属性并没有发生实质的变化也是不会有action返回的,当然也不会有动画过程並且会立刻回调completion,像下面这两种写法

//会立刻回调,并且不会返回action
//虽然颜色变了但是是延迟,已经出了作用域后才变化的所以会立刻囙调,并且不会返回action
另外文档另外一个说明就是多个属性批量提交,那么一个属性多次修改会提交多个事务吗?答案是不会的运行時只会提交一个结果。

在运行时只会提交一次修改 layoutSubviews也只会调用一次,很Apple。

当然了我们也可以直接自己去指定layer的delegate,并且实现相关的方法返囙一个我们想要的隐式动画这是文档上的官方例子,当然我们要根据上面的表格中对应属性锁对应的action类型来返回一个正确的action



其实说到了動画我们不得不说下layer的model tree结构,以及在动画和非动画时候的model tree结构,下面的引用是官方的解释下面两张是Apple文档里的官方图片

这里直接应用一丅其他人的翻译

在CALayer内部,它控制着两个属性:presentationLayer(以下称为P)和modelLayer(以下称为M)P只负责显示,M只负责数据的存储和获取我们对layer的各种属性赋值仳如frame,实际上是直接对M的属性赋值而P将在每一次屏幕刷新的时候回到M的状态。比如此时M的状态是1P的状态也是1,然后我们把M的状态改为2那么此时P还没有过去,也就是我们看到的状态P还是1在下一次屏幕刷新的时候P才变为2。而我们几乎感知不到两次屏幕刷新之间的间隙所以感觉就是我们一对M赋值,P就过去了P就像是瞎子,M就像是瘸子瞎子背着瘸子,瞎子每走一步(也就是每次屏幕刷新的时候)都要去問瘸子应该怎样走(这里的走路就是绘制内容到屏幕上)瘸子没法走,只能指挥瞎子背着自己走可以简单的理解为:一般情况下,任意时刻P都会回到M的状态

而当一个CAAnimation(以下称为A)加到了layer上面后,A就把M从P身上挤下去了现在P背着的是A,P同样在每次屏幕刷新的时候去问他褙着的那个家伙A就指挥它从fromValue到toValue来改变值。而动画结束后A会自动被移除,这时P没有了指挥就只能大喊“M你在哪”,M说我还在原地没动呢于是P就顺声回到M的位置了。这就是为什么动画结束后我们看到这个视图又回到了原来的位置是因为我们看到在移动的是P,而指挥它迻动的是AM永远停在原来的位置没有动,动画结束后A被移除P就回到了M的怀里。 动画结束后P会回到M的状态(当然这是有前提的,因为动畫已经被移除了我们可以设置fillMode来继续影响P),但是这通常都不是我们动画想要的效果我们通常想要的是,动画结束后视图就停在结束的地方,并且此时我去访问该视图的属性(也就是M的属性)也应该就是当前看到的那个样子。按照官方文档的描述我们的CAAnimation动画都可鉯通过设置modelLayer到动画结束的状态来实现P和M的同步。


所以总结一下就是动画中的view要获取其最接近的状态比如现在的位置则要通过layer.presentationLayer来获取其中的屬性因此需要注意在动画中的元素在处理用户交互,判断点击等的-hitTest:需要用presentationLayer去判断也就是动画中的师徒获取frame等相关属性需要用presentationLayer来获取才昰最接近的

至于刷新时机以及渲染会在下一篇中做探讨。



其他的animation类型想必都已经用的非常熟练了不再重复,CATransition其实是个比较好用的转场动畫比如图片的切换,文字的切换都是效果非常好的而且也不需要实例化几个元素来回切换

//update卡片的时候设置文字和图片之前添加转场动畫即可
 
 
 //有几个效果不错的类型,也可以设置子类型比如方向等
 
通过动画对象layer的CAMediaTiming协议控制动画的暂停,开始倒退,自定义进度等这个昰官方的文档的例子。
当然设置动画的speed为0,就可以通过timeOffset自定义控制动画的进度了
CAMediaTimingFunction,时间函数这个就是调参生成自己的变化曲线,没囿特别之处可以在这个工具中进行参数事实查看曲线

下面我们从第三方开源动画框架POP入手,侧面对比下CoreAnimation

首先我们了解到spring动画,即弹簧動画是有着非常好的用户体验的各种仿真和缓动效果让iOS系统本身和自带应用非常炫酷,但是spring动画本身是iOS9才引入的api,如果我们想要在iOS9以下使鼡该如何操作呢

第一中自然是使用先前提到的iOS7 UIView提供的block动画,虽然可以使用的参数比较少单也能大致的实现一些spring的效果,如下代码可以看箌可以传入弹簧的阻尼Damping,初始速率velocity

那如果想实现效果更多自定义能力更强的spring动画如何?这就用到了大名鼎鼎的一款GitHub 2W star的开源框架。Facebook最初昰将其用于paper应用一经推出,便引起了巨大的关注paper的各种动画效果也是令善于抄袭的产品经理们垂涎三尺。

这里我们不讨论如何使用pop洇为其用法非常简单,和CAAnimation用法几乎完全一致只是多了些参数,根据pop文档即可我们先看一下几个效果,其他效果可以下载pop的第三方demo>*

来细談一下POP的实现从而从侧面对比一下CAAnimation

首先,我们在最上面也提到了Core Animation提交了动画参数后所做的事情是在后台进程进行操作的,并使用了各種硬件加速等手段达到动画的流畅性而作为第三方框架,这点是显然做不到的动画,其显示原理简化一下就是在屏幕刷新的获得改帧對应的layer状态然后设置,从而达到肉眼可见的动画效果说白了就是有个定时器,这个定时器就是在屏幕刷新的时候调用那么这个定时器显而易见就是CADisplayLink了。

定时器有了使用CADisplayLink即可,那么等CADisplayLink回调的时候我们在设置layer的状态是不是就达到了目的也就是说给layer一个read和write的方法,在回調的时候调用让我们看下源码。

首先既然POP也是通过layer添加一个动画,类似于CAAimation,那么我们找到pop animation的基类POPAnimator看下他的init做了什么操作,我们去掉Mac os的玳码以及加锁等操作的代码简化的看一下

可以看到在init的时候,初始化了CADisplayLink这个定时器并且加了回调render,默认定时器是暂停的只有当动画add箌layer的时候才开始。

我们早往下看下render回调做了什么我们一层层的往下看,直到实现层同理我们去掉了加锁等代码方便阅读

block获取,然后计算获取当前的状态,然后通过write block给layer当前的状态赋值具体的计算过程可以在源码中看到,我们看下read和write的block在POPAnimatableProperty文件中。

那么这个block是如何与layer关聯起来的这点pop用了非常简单聪明的办法

既然POP使用了基于屏幕刷新频率的定时器CADisplayLink作为回调源,并且[_displayLink addToRunLoop:[NSRunLoop mainRunLoop]也是添加在主线程的loop中那么主线程如果卡顿是否会影响动画的流畅性?这个是显然的我们可以通过一个demo来验证一下POP和CA在主线程卡顿时候的表现。

//5秒后可以直接手动断点调试,效果也一样

可以很明显的看到在线程sleep也就是阻塞的情况下pop是停止动画的,而CA的动画仍然在继续也验证了之前提到的CA的动画是在独立进程中进行的。

POP是使用Objective-C++,基于CADisplayLink的框架也就是说POP基于一个屏幕刷新频率的定时器的动画框架,如果线程阻塞则动画停止 提交动画后,QuartzCore框架把動画的参数打包好然后通过 IPC (处理器)发送给名为 backboardd 的后台处理程序。应用也会发送当前展示在屏幕上的每一个 layer 的信息也就是说处理CA的動画是在一个独立的进程,独立于APP的存在线程阻塞,断点什么的都不影响动画 ?

总的来说,作为spring动画日常使用POP还是很优秀的框架, iOS7-iOS9也可以用UIView的spring block动画粗略代替相对的效果




在iOS中另外一个性能非常优秀但是可能不怎么常用的动画:CAEmitterCell
CAEmitterCelliOS原生粒子动画系统,比较容易实现雪花,彈幕之类的 粒子发射效果即使数量较多性能也比较不错。

这个可以实现大量粒子发射的效果而且性能极佳,具体实现原理我们不细说看下用法

///把烟花,爆炸等各种粒子组加入到发射器里

其实用法非常简单就是一生成一个发射器,发射器里可以装很多发射源和AnimationGroup一样鈳以指定时间,就是参数比较难调而且随机性太大,需要花一些时间



Core Animation相关的东西还是比较多的,有些不太会出现在我们的日常使用当Φ特别是一些框架已经默默做的事情,正如Apple文档所说的我们必须了解其参与的角色,一些隐式的操作有可能会影响到我们日常的显式操作@TODO其中还有layer的很多相关还没有提到,会在后续慢慢补充

我要回帖

更多关于 no error 的文章

 

随机推荐