UIKit Dynamics 的性能问题

Posted

技术标签:

【中文标题】UIKit Dynamics 的性能问题【英文标题】:Performance Issue With UIKit Dynamics 【发布时间】:2014-06-25 01:02:33 【问题描述】:

最近在玩 Stuart Hall 的 UIKit Dynamics 教程 (http://stuartkhall.com/posts/flipcase-bounce-in-uikit-dynamics) 时,我发现存在性能问题。

在我向动画师添加了大约 50 个项目(弹跳球)后,应用程序变得非常缓慢——几乎冻结了。分析显示 [UIDynamicAnimator _animatorStep] 占用了 96% 的 CPU。

有人知道如何提高具有大量 UIDynamicItem 的 UIKit Dynamics 应用的性能吗?

您可以下载我的代码并自己查看性能问题:

https://www.dropbox.com/s/zy7ajj6molxm9up/Flipper-UIKit-Dynamics-Poor-Performance.zip

以下是发生一切的代码:

#import "ViewController.h"
#import <CoreMotion/CoreMotion.h>

@interface ViewController () 
    CMMotionManager *_motionManager;
    NSOperationQueue *_queue;

    int _count;


@property(nonatomic, strong) UIDynamicAnimator *animator;
@property(nonatomic, strong) UIGravityBehavior *gravityBehavior;
@property(nonatomic, strong) UICollisionBehavior *collisionBehavior;
@property(nonatomic, strong) UIDynamicItemBehavior *bounceBehaviour;

@end

@implementation ViewController

static NSInteger const kBallSize = 25;

- (void)startMotion 
    if (_motionManager == nil) 
        _queue = [[NSOperationQueue alloc] init];
        _motionManager = [[CMMotionManager alloc] init];
        _motionManager.deviceMotionUpdateInterval = 0.1;
    
    [_motionManager startDeviceMotionUpdatesToQueue:_queue withHandler:^(CMDeviceMotion *motion, NSError *error) 
//        angleLabel.text = [NSString stringWithFormat:@"%g", motion.attitude.pitch];
        self.gravityBehavior.angle = atan2(motion.gravity.x + motion.userAcceleration.x, motion.gravity.y + motion.userAcceleration.y); // motion.attitude.pitch + M_PI / 2.0;
    ];


- (void)stopMotion 
    if (_motionManager) 
        [_motionManager stopDeviceMotionUpdates];
    

    _motionManager = nil;


- (void)viewDidLoad 
    [super viewDidLoad];

    [self startMotion];

    // Simple tap gesture
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
    [self.view addGestureRecognizer:tapGesture];

    // Create our animator, we retain this ourselves
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

    // Gravity
    self.gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[]];
    self.gravityBehavior.magnitude = 10;
    [self.animator addBehavior:self.gravityBehavior];

    // Collision - make a fake platform
    self.collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[]];

    UIBezierPath *aPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(284, 160)
                                                         radius:160
                                                     startAngle:(CGFloat) (M_PI * 0.0)
                                                       endAngle:(CGFloat) (M_PI * 2)
                                                      clockwise:YES];

    [self.collisionBehavior addBoundaryWithIdentifier:@"bottom" forPath:aPath];
    self.collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
    [self.animator addBehavior:self.collisionBehavior];

    CAShapeLayer *layer = [CAShapeLayer layer];
    layer.path = aPath.CGPath;
    layer.fillColor = [UIColor lightGrayColor].CGColor;

    [self.view.layer addSublayer:layer];

    // Bounce!
    self.bounceBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:@[]];
    self.bounceBehaviour.elasticity = 0.75;
    self.bounceBehaviour.resistance = 0.1;
    self.bounceBehaviour.friction = 0.01;
    [self.animator addBehavior:self.bounceBehaviour];


- (void)didReceiveMemoryWarning 
    [super didReceiveMemoryWarning];


- (BOOL)prefersStatusBarHidden 
    return YES;


#pragma mark - Gesture Recognizer

- (void)onTap:(UITapGestureRecognizer *)gesture 
    if (gesture.state == UIGestureRecognizerStateEnded) 
        // Grab the x of the touch for the center of our ball
        // Ignore the y, we'll drop from the top
        CGPoint pt = [gesture locationInView:self.view];
        [self dropBallAtX:pt];
    


#pragma mark - Helpers

- (void)dropBallAtX:(CGPoint)p 
    _count++;
    self.label.text = [NSString stringWithFormat:@"Balls: %d", _count];

    // Create a ball and add it to our view
    UIView *ball = [[UIView alloc] initWithFrame:CGRectMake(p.x - (kBallSize / 2), p.y - (kBallSize / 2), kBallSize, kBallSize)];
    CGFloat hue = (arc4random() % 256 / 256.0);               //  0.0 to 1.0
    CGFloat saturation = (arc4random() % 64 / 256.0) + 0.75;  //  0.5 to 1.0, away from white
    CGFloat brightness = (arc4random() % 64 / 256.0) + 0.75;
    ball.backgroundColor = [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1.0];
    ball.layer.cornerRadius = kBallSize / 2;
    ball.layer.masksToBounds = YES;
    [self.view addSubview:ball];

    // Add some gravity
    [self.gravityBehavior addItem:ball];

    // Add the collision
    [self.collisionBehavior addItem:ball];

    // Add the bounce
    [self.bounceBehaviour addItem:ball];


@end

【问题讨论】:

【参考方案1】:

这让我觉得这是一个非常棘手的问题,每个球都会影响它的邻居,这反过来又会递归地影响他们的邻居,等等。我并不惊讶这个非线性复杂性问题会遭受相当严重的性能下降,因为你添加更多的球。我发现当你接近 30-40 个球(在 iPhone 5 上)时,fps 开始明显下降,就像你说的,当你接近 50 个球时,它很快接近 1-2 fps。如果你让它达到静止状态(如果你在混合中保持CMMotionManager 这不太可能),几秒钟后 fps 恢复,但一旦你丢下另一个球或CMMotionManager 再次改变重力,复杂性这个问题很快让 fps 再次陷入困境。

简单的修复(比如增加摩擦力、减少弹跳、引入角阻力、移动到矩形形状、通过添加附件行为将项目停止移动后固定在一个位置(无论如何,如果使用CMMotionManager,这没有意义)等)似乎有适度的影响。让我感到震惊的是,解决此问题的唯一方法是放弃 UIKit Dynamics 以采用其​​他方法,这些方法可以根据您的特定问题做出一些简化假设(例如,因为您正在处理角对称的圆形对象,您可以消除旋转复杂性,您可以使用更简单的碰撞逻辑,调整一些上述变量以更快地抑制行为;等等)。我不熟悉任何可以为您做到这一点的框架,因此这可能需要大量的代码。即使你这样做了,你仍然在处理非线性复杂性问题,并且性能可能在某个时候仍然会下降。

【讨论】:

是的,我认为这可能是 UIKit Dynamics 的限制。我相信 SpriteKit 会更好地处理这个问题。 @YuchenWang 也许,但我怀疑,最终,你会遭受同样的组合爆炸,每个球都会导致其他 50 个球发生级联变化(反过来,每个球都会影响其他球)。但是,可能值得尝试 SpriteKit。令我震惊的是,您需要某种方法来限制问题(例如,一段时间后将球固定在某个位置(或者在您以编程方式推断出其他球无法再击中之后)。这是一个不平凡的问题。 实际上,如果您看一下 Jawbone 的应用程序“Up Coffee”所做的动画,这正是我在示例应用程序中所做的,但动画效果要好得多。我检查了他们的应用程序并没有找到任何 3rd 方库。所以他们必须使用 UIKit Dynamics 来做到这一点。我很好奇他们是如何做到这一点的。 这里是应用程序的链接:itunes.apple.com/us/app/up-coffee/id828031130?mt=8 @YuchenWang,“所以他们必须使用 UIKit Dynamics”。 SpriteKit 不是第三者,是吗?

以上是关于UIKit Dynamics 的性能问题的主要内容,如果未能解决你的问题,请参考以下文章

UIKit Dynamics 和 UIKit Animation 有啥区别?

UIKit Dynamics:识别圆形和边界

UIKit Dynamics UICollisionBehavior 无反弹的碰撞

UIKit Dynamics 碰撞 - 保持 Barriers 元素静止

尝试使用 UIKit Dynamics 让 UIView 反弹

查询性能问题:Dynamics CRM s-s-rS 报告的查询案例实体(事件)