在前一篇文章《尝鲜Realm》,我们了解到Realm标榜其为专注于移动平台的数据库,那既然是数据库,我们当然要看最基本的CRUD(Create/Read/Update/Delete)操作。当然在做这些基本的操作之前还是需要学习下Realm的基本知识。

pop是Facebook在开源的一款动画引擎,看下其官方的介绍:

在一个ios应用中,有时候我们只需要某个类的一个实例。即是在整个项目中,这个类的对象只能被初始化一次。

当使用SQL时,首先我们会设计关系数据库的结构,比如设计几个表,每个表里面哪些是主键、对哪些键做索引、默认值是什么等等,并将这些用SQL表示好,然后调用函数执行创建表的SQL语句或者通过一些ORM工具如:FMDB的函数创建表格,然后还要对应的创建一个class/struct来表示这个数据结构。而Realm抛弃了这繁琐的中间语言,而采用目标语言(比如Objective-C/Swift/Java)本身作为DSL(domain-specific
language)来描述同样的类似表格的数据结构,由于是目标语言,其自身就能表达一个数据结构,因此带有了一些Model层属性,比如SQL很难描述某列的属性是另一个结构,需要自己定义数据结构来表示,而Realm的DSL则自动包含了这层定义。

Pop是一款在iOS、tvOS和OS
X平台通用的可扩展动画引擎。它在基本静态动画的基础上,增加了弹性以及衰减动画,这在创建真实有物里性的交互很有用。其API能够快速的整合进已有的Objective-C工程,可以对任意对象的任意属性做动画。这是一个成熟且经过测试的框架,在Paper这款优秀的app中有广泛的应用。(iOS7之后苹果也提供了Spring动画(不过CASpringAnimation
iOS9才提供)以及UIDynamic物理引擎(比如碰撞以及重力等物理效果不错,有兴趣可以玩玩))

澳门微尼斯人手机版,例如,当应用程序启动时,应用的状态由UIApplication类的一个实例维护,这个实例则代表了整个”应用程序对象”,它只能是一个实例,其作用是实现整个应用程序中一些共享资源的访问和状态的保持等。

我们来看个例子,描述省与城市的天气:省有其天气属性,同时还有其包含的城市;而城市也有天气属性。用SQL的话,我们可能会设计如下三个表:

那Pop动画引擎跟CoreAnimation有啥区别?我们先来简单了解一下苹果的CoreAnimation:

说通俗点,通常代表一些物理设备,比如打印机。或是某种不可以有多个实例同时存在的虚拟资源或是系统属性比如一个程序的某个引擎或是数据。用单例模式加以控制是非常有必要的。

// table t_provinceCREATE TABLE t_provience ( f_provience_id int, f_proviecne_name varchar, f_weather varchar PRIMARY KEY (f_province_id))// table f_cityCREATE TABLE t_city ( f_city_id int, f_city_name varchar, f_city varchar PRIMARY KEY // relationship of city and provinceCREATE TABLE t_province_city ( f_provience_id int, f_city_id int, PRIMARY KEY (f_proviece_id))

CoreAnimation

先看下CoreAnimation在框架中所处的位置:

澳门微尼斯人手机版 1CoreAnimation.png

可以看出视图的渲染以及动画都是基于CoreAnimation框架(看名字容易以为只是动画相关),其地位还是相当重要。我们来看下iOS在视图的渲染以及动画的各个阶段都发生了虾米,这其中涉及到应用内部以及应用外部:

应用内部4个阶段:

  • 布局这个阶段是用户在程序内部设置组织视图或图层的关系,比如设置view的backgroundColor、frame等属性;

  • 显示这是图层的寄宿图片被绘制的阶段,比如实现了-drawRect:或-drawLayer:inContext:方法,这些方法会这这个阶段执行,这些绘制方法是由CPU在应用内部同步地完成,属于离屏渲染。

  • 准备这个阶段,CoreAnimation框架会将渲染视图的各种属性以及动画的参数等数据准备好;同时这个阶段还会解压需要渲染的image。

  • 提交这是在应用内部发生的最后阶段,CoreAnimation打包准备好的所有视图/图层以及动画的属性,然后通过IPC发送到render
    server进行显示,可以看到其实视图的渲染以及动画是在另外一个进程处理的。在iOS5和之前的版本是SpringBoard进程(同时管理着iOS的主屏),在iOS6之后的版本中叫做BackBoard。

应用外部2个阶段:一旦这些打包好的数据到达render
server,这些数据会被反序列化成另一个叫做渲染树的图层树,根据这个树状结构,render
server做如下工作:

  • 根据layer的属性值,如果图层包含动画,则计算其属性的中间插值,然后设置OpenGL几何形状来执行渲染
  • 在屏幕上渲染可见的三角形

所以整个阶段包含六个阶段,如果有动画,最后两个阶段会重复的执行。前五个阶段都是通过CPU处理的,只有最后一个阶段使用GPU。而且你能控制的只有前面两个阶段:布局和显示,剩下都是CoreAnimation框架在内部进行处理。

简单了解完CoreAnimaton的工作方式之后,我们在来看看pop实现动画的方式。

OS中好几个类都是采用了单例模式,比如NSApplication, NSFontManager,
NSDocumentController,NSHelpManager, NSNull,NSProcessInfo,
NSScriptExecutionContext, NSUserDefaults。

表t_province表示省份信息,表f_city表示城市信息,这里为了凸显要表示的二者之间的关系,用了一个表t_province_city来表示一个省有几个城市。然后在程序中可能还要定义两个结构Province和City。

pop

CADisplayLink是一个和屏幕刷新率相同的定时器,pop实现的动画就是基于该定时器,它在每一帧计根据指定的time
function计算出动画的中间值,然后将计算好的值赋给视图或图层的属性(比如透明度、frame等),当属性发生变化之后,我们知道Core
Animation会通过IPC把这些变化通知render
server进行渲染,因此整个动画过程变成是你的应用内部驱动的,render
server则被动接受数据进行渲染,跟上面提到的Core
Animation动画方式有所不同;另一个不同是pop在动画过程中改变的是model
layer的状态,不像Core Animation作用的是渲染树的图层树,Core
Animation动画会在动画结束后回到起始位置, model layer, presentation
layer 和 render layer的区别有兴趣可以去了解。

澳门微尼斯人手机版 2core_animation_basics_sublayer_hierarchies.png

OC实现原理:

那同样的意思如何用Realm来表示呢?

Animate View

pop提供了几种动画,包括basic、Spring、Deacy以及自定义的动画

澳门微尼斯人手机版 3pop
animation

其API跟Core
Animation提供的API类似,我们来看看如何使用pop,包括以下几个步骤:

// 1 选择动画类型 (POPBasicAnimation POPSpringAnimation POPDecayAnimation)POPSpringAnimation *springAnimation = [POPSpringAnimation animation];springAnimation.springBounciness=16;springAnimation.springSpeed=6;// 2 选择要对视图或者图层的属性做动画,比如我们想要缩放动画,我们可以选择:kPOPViewScaleXY。//pop提供了一些属性,包括视图属性:kPOPViewAlpha kPOPViewBackgroundColor kPOPViewBounds kPOPViewCenter kPOPViewFrame等,//图层属性:kPOPLayerBackgroundColor kPOPLayerBounds kPOPLayerScaleXY kPOPLayerSize kPOPLayerOpacity kPOPLayerPosition等,具体可以查看POPAnimatableProperty.m文件springAnimation.property = [POPAnimatableProperty propertyWithName:kPOPViewScaleXY];// 3 设置动画的终点值springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake];// 4 为动画指定代理POPAnimatorDelegate,springAnimation.delegate = self;// 5 将动画添加到视图或图层中,开始做动画[_testView pop_addAnimation:springAnimation forKey:@"springAnimation"];

可以看到API与Core
Animation的基本类似,熟悉的同学应该能很快使用上,具体的使用方式可以尝试,比如Spring动画的几个参数的效果,实践出真知~

pop除了可以对view或着layer做动画之外,还可以对任意NSObject对象的属性做动画,其实动画本质上也是离散的,当每秒内离散的数据足够多的时候对于人眼来说就是连续的。因此对NSObject对象属性做动画本质上也是计算出一系列的离散值,比如对下面的对象做动画,然后我们可以根据这些离散值来观察pop的动画曲线:

@interface AnimatableObject : NSObject@property (nonatomic,assign) CGFloat propertyValue;@end@implementation AnimatableObject- setPropertyValue:newValue{ _propertyValue = newValue;}@end

上面的对象包含一个float类型的属性,由于这个对象的属性并不是pop提供的内建属性(POPAnimatableProperty.mm中定义的),因此我们需要创建一个新的动画属性POPAnimatableProperty:

POPAnimatableProperty *valueProperty = [POPAnimatableProperty propertyWithName:@"value" initializer:^(POPMutableAnimatableProperty *prop) { prop.writeBlock=^(id obj, const CGFloat values[]) { [obj setPropertyValue:values[0]]; [_values addObject:@(values[0])]; //收集值用于后面绘制观察曲线 }; prop.readBlock = ^(id obj, CGFloat values[]) { values[0] = [obj propertyValue]; };}];

我们需要为这个动画属性提供名称以及writeBlock跟readBlock,block里面定义如何将数值与对象属性关联,现在我们对这个对象做动画并绘制相关的动画曲线。我们对object做basic动画,采用easeInOut的时间函数:

POPBasicAnimation *animation = [POPBasicAnimation animation];animation.property = valueProperty;animation.fromValue = [NSNumber numberWithFloat:0];animation.toValue = [NSNumber numberWithFloat:100];animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];animation.duration = 1.5;animation.completionBlock = ^(POPAnimation *anim, BOOL finished){ [self drawCurl:_values];};_animateObject = [[AnimatableObject alloc] init];[_animateObject pop_addAnimation:animation forKey:@"easeInEaseOut"];//根据获取到的值来绘制曲线-drawCurl:values{ UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake]; for (int i=0; i<[values count]; i++) { NSNumber *value = values[i]; CGPoint point = CGPointZero; point.x = 100+i*(100/values.count); point.y = 350 - [value floatValue]; [path addLineToPoint:point]; } _layer.path = path.CGPath; [self.view.layer addSublayer:_layer];}

可以看到绘制出如下的曲线:

澳门微尼斯人手机版 4easeInEaseOut

假如使用PopSpringAnimation做动画:

POPSpringAnimation *springAni = [POPSpringAnimation animation];springAni.property = valueProperty;springAni.fromValue = [NSNumber numberWithFloat:0];springAni.toValue = [NSNumber numberWithFloat:100];springAni.dynamicsMass = 5;springAni.completionBlock = ^(POPAnimation *anim, BOOL finished){ [self drawCurl:_values];};_animateObject = [[AnimatableObject alloc] init];[_animateObject pop_addAnimation:springAni forKey:@"springAnimation"];

可以看到是如下曲线,有兴趣可以自己是试试其它曲线。

澳门微尼斯人手机版 5spring

//Singleton.h

用Realm的话只需要定义两个结构就可以了:

实现原理

简单了解完pop的使用方式,我们来继续聊一聊pop的实现方式,为了方便说明简单分析下面的pop动画,移动view的x位置:

POPBasicAnimation *basicAnimation = [POPBasicAnimation animation];basicAnimation.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionX];basicAnimation.toValue = @;[_testView pop_addAnimation:basicAnimation forKey:nil];
  • pop内建属性

kPOPLayerPositionX是pop内建的属性,pop内置了常见的属性动画,保存在全局的静态数组_staticStates[]中,对每个属性定义好了读取属性值readBlock以及写入属性值的writeBlock(如果是自定义的属性,则需要自己实现readBlock和writeBlock,如之前所示),

static POPStaticAnimatablePropertyState _staticStates[] ={ ... {kPOPLayerPositionX, ^(CALayer *obj, CGFloat values[]) { values[0] = [(CALayer *)obj position].x; }, ^(CALayer *obj, const CGFloat values[]) { CGPoint p = [(CALayer *)obj position]; p.x = values[0]; [obj setPosition:p]; }, kPOPThresholdPoint }, ... }
  • POPAnimatorpop的动画都是交给POPAnimator执行的,POPAnimator是一个负责执行动画单例对象,这个对象会开启一个CADisplayLink定时器,该定时器会在每帧执行动画:

//POPAnimator.mm- init{ ... _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector]; _displayLink.paused = YES; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; ...}

可以发现定时器是加到runloop的NSRunLoopCommonModes模式中的,这样即便是UI滑动的时候也不会影响动画的执行。

当我们使用pop_addAnimation把定义好的动画加到POPAnimator对象时:

- addAnimation:(POPAnimation *)anim forObject:obj key:(NSString *)key{ ... //POPAnimator会先判断该动画对象是否存在(所有动画会保存在内部的一个字典对象中)了,如果存在就不重复添加执行动画 NSMutableDictionary *keyAnimationDict = (__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj); if (nil == keyAnimationDict) { keyAnimationDict = [NSMutableDictionary dictionary]; CFDictionarySetValue(_dict, (__bridge void *)obj, (__bridge void *)keyAnimationDict); } else { POPAnimation *existingAnim = keyAnimationDict[key]; if (existingAnim) { if (existingAnim == anim) { return; } [self removeAnimationForObject:obj key:key cleanupDict:NO]; } } keyAnimationDict[key] = anim // 将动画保存在_pendingList数组中 _pendingList.push_back; // 开启CADisplayLink定时器 updateDisplayLink; //执行_pendingList数组中的动画 [self _scheduleProcessPendingList];}
  • 基于NSRunLoop的动画更新机制当我们有动画需要被执行时,pop会在主线层的runloop中添加观察者,监听kCFAllocatorDefault、kCFRunLoopBeforeWaiting和kCFRunLoopExit事件,并在回调的时候处理执行_pendingList里的动画

- _scheduleProcessPendingList{ ... if (!_pendingListObserver) { __weak POPAnimator *weakSelf = self; _pendingListObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting | kCFRunLoopExit, false, POPAnimationApplyRunLoopOrder, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { ... //在回调中执行_pendingList中的动画 CFTimeInterval time = [self _currentRenderTime]; [self _renderTime:(0 != _beginTime) ? _beginTime : time items:_pendingList]; ... }); if (_pendingListObserver) { CFRunLoopAddObserver(CFRunLoopGetMain(), _pendingListObserver, kCFRunLoopCommonModes); } } ...}
  • 渲染 pending
    动画当runloop观察者的回调被执行时,POPAnimator会根据当前时间(需要这个时间去做插值)一个一个执行_pendingList里的动画:

- _renderTime:(CFTimeInterval)time item:(POPAnimatorItemRef)item{ ... // 只执行有效的动画 if (state->active && !state->paused) { //根据当前时间执行动画 applyAnimationTime(obj, state, time); //如果动画执行完毕 if (state->isDone { //将计算好的值设给视图或图层对象 applyAnimationToValue(obj, state); } } ...}static void applyAnimationTime(id obj, POPAnimationState *state, CFTimeInterval time){ //根据当前时间计算推倒出新的值大小 if (!state->advanceTime(time, obj)) { return; } POPPropertyAnimationState *ps = dynamic_cast<POPPropertyAnimationState*>; if (NULL != ps) { //将推倒出的新值作用到视图或图层对象 updateAnimatable; }}

pop会根据动画类型做不同的插值算法,如下所示可以看到有四种不同的插值方式

bool advanceTime(CFTimeInterval time, id obj) { ... switch  { case kPOPAnimationSpring: advanced = advance(time, dt, obj); break; case kPOPAnimationDecay: advanced = advance(time, dt, obj); break; case kPOPAnimationBasic: { advanced = advance(time, dt, obj); computedProgress = true; break; } case kPOPAnimationCustom: { customFinished = [self _advance:obj currentTime:time elapsedTime:dt] ? false : true; advanced = true; break; } ...}

我们以kPOPAnimationBasic方式为例,

bool advance(CFTimeInterval time, CFTimeInterval dt, id obj) { //默认采用kCAMediaTimingFunctionDefault时间函数 ((POPBasicAnimation *)self).timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; // 将时间归一化到[0-1] CGFloat p = 1.0f; if (duration > 0.0f) { // cap local time to duration CFTimeInterval t = MIN(time - startTime, duration) / duration; p = POPTimingFunctionSolve(timingControlPoints, t, SOLVE_EPS); timeProgress = t; } else { timeProgress = 1.; } //根据当前的时间,以及from和to的值计算出新的当前值 interpolate(valueType, valueCount, fromVec->data(), toVec->data(), currentVec->data; progress = p; }

计算出新的值后,便可以通过内建属性定义好的writeBlock将新的值付给UI对象:

static void updateAnimatable(id obj, POPPropertyAnimationState *anim, bool shouldAvoidExtraneousWrite = false){ pop_animatable_write_block write = anim->property.writeBlock; if (NULL == write) return; write(obj, currentVec->data;}

pop动画的过程大体上如上所示,也就是在每一帧将通过不同的曲线函数计算出新的插值并赋给UI对象,以此来实现动画

下面我们来看看如何通过pop来实现一个自定义的动画,pop对自定义动画的支持感觉比较单一,可以认为就是一个定时器的功能而已。。。想要自定义动画我们就需要有一个自定义的函数曲线,比如我们要实现一个弹簧动画(跟spring动画类似),我们使用如下的时间函数,输出为[0-1](更多的缓动函数可以去这查看:

float ElasticEaseOut{ return sin(-13 * M_PI_2 *  * pow(2, -6 * p) + 1;}

当有了定义好的缓动曲线后,我们就可以通过POPCustomAnimation来实现自定义动画,POPCustomAnimation会在每次CADisplayLink定时器触发时回调我们定义好的函数,同时给我们传递相关的时间参数:

POPCustomAnimation *customAni = [POPCustomAnimation animationWithBlock:^BOOL(id target, POPCustomAnimation *animation) { //动画开始的时间,我们可以记录下来作为基准时间 if(_baseTime == 0){ _baseTime = animation.currentTime; } //根据当前时间,计算出当前的时间进度,并根据动画周期归一化到[0-1] double progress = (animation.currentTime - _baseTime)/_duration; //使用ElasticEaseOut自定义曲线根据当前进度计算出新的值,该值大小也为[0-1] double caculateValue = ElasticEaseOut; //根据缓动函数的输出,计算新的值,并赋给UI对象 CGPoint current = CGPointZero; current.x = _from.x + (_to.x - _from.x) * caculateValue; current.y = _from.y + (_to.y - _from.y) * caculateValue; _testView.frame = CGRectMake(current.x, current.y, 20, 20); //如果当前进度小于1,则继续动画 if(progress < 1.0){ return YES; } return NO; }]; [_testView pop_addAnimation:layCus forKey:@"custom"];

可以看到如下的弹簧效果,与spring效果类似:

澳门微尼斯人手机版 6ElasticEaseOut

@interface Singleton:NSObject

@interface City : RLMObject@property int id;@property NSString *name;@end@interface Province : RLMObject@property int id;@property NSString *name;@property RLMArray<City *><City> *cities;@end
总结

通过上面的介绍我们大概也了解了pop动画引擎了,pop相比iOS的coreanimation的优势在于提供了spring以及decay动画效果,iOS7的spring动画效果较弱,CASpringAnimation能够提供的效果较好,不过需要iOS9或以上的版本,除此之外pop还允许你自定义动画,所以pop还是有一定的吸引力。不过我们也可以发现pop动画是在主线层执行的,因此如果主线层做耗时操作的话,动画就不那么流畅了,有兴趣可以试一试。。。

+(Singleton *)sharedManager;

这里定义了两个结构:Provience和City。首先他们都需要继承Realm的RLMObject。City定义了name和id属性;Province除了定义了name和id还定义了了一个cities成员,而且其定义十分诡异:

参考:

ios核心动画高级技巧pop缓动函数

@property (nonatomic,strong)NSString *singletonData;

  • RLMArray: 表示一个RLMArray对象,可以认为是一个NSArray对象
  • <City *>: 可以认为是每个Array成员的类型.
  • <City>:
    序列化的结构,按照找个类定义的成员和相关辅助函数来做序列化操作.

@end

所以上面就是定义了: 一个按照结构City进行存储的City *的数组的指针。

//Singleton.m

其实就可以认为是City对象的数组就可以了。只是写法比较诡异。因为有这个数组的存在来维护“1
to
n”的关系,所以也就不需要SQL里面定义的关系表了,而且已经用目标语言对数据结构进行了描述。

#import”Singleton.h”

这样看来是不是觉得Realm会更简单呢?

@implementation Singleton

看了上面还不够,我们来看个例子,看看是不是操作简单、容易理解并且动作高效。

@sysnthesize singletonData=_singletonData;

这里我准备了一个学生信息管理界面(实际上就一个学生名和年纪),可以添加、查询、搜索以及删除学生:

static Singleton *sharedManager=nil;

澳门微尼斯人手机版 7app

+(Singleton *)sharedManager {

代码主要是由OC来实现的(下面的接口也是以OC为例讲解),具体的demo可以在github进行下载。

static dispatch_once_t_once;

realm的操作均由一个RLMRealm来管理,我们称之为realm,可以把他理解为一个数据库。而其他继承自RLMObject都是一个个的数据表。通过:

dispatch_once(&once,^{

发表评论

电子邮件地址不会被公开。 必填项已用*标注