与Swift上的iOS中的drawRect有关的性能问题

时间:2015-02-18 01:41:11

标签: ios multithreading swift bitmap drawrect

感谢您抽出宝贵时间阅读我的帖子。最近我一直在开发一个简单的iOS自由绘制应用程序,并且遇到了一个目前高于我的技能水平要解决的问题。我已经在互联网上搜索了几天试图提出一个解决方案但到目前为止没有运气。幸运的是,我已经想到了应用程序延迟问题的补救措施,但是我仍然需要帮助,因为我们真的不知道如何实现它。

该计划的这一部分如何运作的简要说明:

当用户在屏幕上移动他的手指时(在标有:view_Draw的UIView中),touchesBegan()和touchesMoved()解释x& y坐标的起点和终点并将这些坐标存储在一个数组(行)中。然后强制drawView通过setNeedsDisplay()更新。

class view_Draw: UIView {

//  view_Draw Class Variables

var lastPoint: CGPoint!
var drawColor:UIColor = UIColor.redColor()

required init(coder aDecoder:NSCoder) {
super.init(coder: aDecoder)
self.backgroundColor = UIColor.whiteColor()

}

  override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {

      lastPoint = touches.anyObject()?.locationInView(self)
}


override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {

     var newPoint = touches.anyObject()?.locationInView(self)

     lines.append(Line(start: self.lastPoint, end: newPoint!, color: self.drawColor))
     self.lastPoint = newPoint
     setNeedsDisplay()

}

 override func drawRect(rect: CGRect) {

        var context = UIGraphicsGetCurrentContext()

        //set stroke style
        CGContextSetLineCap(context, kCGLineCapRound)

        //set brush parameters
        CGContextSetStrokeColorWithColor(context, drawColor.CGColor)
        CGContextSetAlpha(context, 0.95)
        CGContextSetLineWidth(context, brushSize)

        for line in lines {

            CGContextBeginPath(context)
            CGContextMoveToPoint(context, line.start.x, line.start.y)
            CGContextAddLineToPoint(context, line.end.x, line.end.y)
            CGContextStrokePath(context)
            //    CGBitmapContextCreateImage(context)
            //    CGBitmapContextReleaseDataCallback()

       }
     }
}

问题的简要说明:

当用户继续在屏幕上绘图时,我注意到在仪器面板中,线程1上的CPU达到了大约95% - 100%。这导致程序中的元素(计时器,绘图响应)开始滞后。

为纠正问题采取的措施:

我通过禁用setNeedsDisplay()进行了实验,并发现填充lines数组仅相当于整体CPU需求的10%。据我所知,这是因为drawRect没有将任何内容应用于行数组中的坐标。

禁用CGContextStrokePath()并启用setNeedsDisplay()会将CPU需求增加到49%。我已将此解释为线条数组中的坐标现在由drawRect操纵 - 但实际上并未绘制到视图上。

这意味着通过强制setNeedsDisplay()在启用CGContextStrokePath的情况下进行更新,它占据了线程1可用处理能力的大约85% - 90%。

我还尝试添加一个计时器来控制setNeedsDisplay强制更新的频率,但结果不太可接受。这种情况下绘图感觉不稳定。

建议的补救措施:

我认为原则问题是setNeedsDisplay()正在重新绘制整个行数组 - 用户绘制的内容,并且正在访问touchesMoved()。

我已经研究过可能会使用GCD尝试从线程1中获取一些负载,但是在阅读之后,似乎这不是一种“安全”方式。根据我的理解,GCD和/或dispatch_async ...不应该用于直接与UI元素交互的元素。

我在各种论坛上看到人们通过将现有路径上下文转换为位图并仅使用setNeedsDisplay更新新生成的路径来解决类似问题。

我希望通过这种方式解决问题,setNeedsDisplay不必每次都会实时绘制整个数组,因为之前绘制的线条已经转换为静态图像。我已经没有关于如何开始实现这个的想法。

正如你可能会说的那样,我几周前才开始学习Swift。我正尽力以有效的方式学习和解决问题。如果您对如何继续这一点有任何建议,我们将不胜感激。再次,谢谢你的帮助。


根据Aky's Smooth Freehand Drawing on iOS tutorial

回答

通过实现以下代码,创建了一个“缓冲区”,它有助于减轻线程1的负载。在我的初始测试中,绘图时的负载高达46%而不是我的原始程序负载出95%-100%

LinearInterpView.swift

import UIKit

class LinearInterpView:UIView {

    var path = UIBezierPath()                                                   //(3)
    var bezierPath = UIBezierPath()



    required init(coder aDecoder:NSCoder) {                                     //(1)

        super.init(coder: aDecoder)
        self.multipleTouchEnabled = false                                       //(2)
        self.backgroundColor = UIColor.whiteColor()
        path = bezierPath
        path.lineWidth = 40.0

    }


    override func drawRect(rect: CGRect) {                                      //(5)

        UIColor.blackColor().setStroke()
        path.stroke()


    }

    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {

        var touch:UITouch = touches.anyObject() as UITouch
        var p:CGPoint = touch.locationInView(self)
        path.moveToPoint(p)

    }

   override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {

        var touch:UITouch = touches.anyObject() as UITouch
        var p:CGPoint = touch.locationInView(self)
        path.addLineToPoint(p)                                                  //(4)
        setNeedsDisplay()

    }

    override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {

        touchesMoved(touches, withEvent: event)
    }

    override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {

        touchesEnded(touches, withEvent: event)
    }


}

CachedLIView.swift

import UIKit

class CachedLIView:UIView {


    var path = UIBezierPath()
    var bezierPath = UIBezierPath()                                             // needed to add this
    var incrementalImage = UIImage()                                            //(1)
    var firstRun:Bool = true



    required init(coder aDecoder:NSCoder) {

        super.init(coder: aDecoder)
        self.multipleTouchEnabled = false
        self.backgroundColor = UIColor.whiteColor()
        path = bezierPath
        path.lineWidth = 40.0


    }


    override func drawRect(rect: CGRect) {

        incrementalImage.drawInRect(rect)                                       //(3)
        path.stroke()


    }

    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {

        var touch:UITouch = touches.anyObject() as UITouch
        var p:CGPoint = touch.locationInView(self)
        path.moveToPoint(p)

    }

    override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {

        var touch:UITouch = touches.anyObject() as UITouch
        var p:CGPoint = touch.locationInView(self)
        path.addLineToPoint(p)
        self.setNeedsDisplay()

    }

    override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {      //(2)

        var touch:UITouch = touches.anyObject() as UITouch
        var p:CGPoint = touch.locationInView(self)
        path.addLineToPoint(p)
        self.drawBitmap()                                                            //(3)
        self.setNeedsDisplay()
        path.removeAllPoints()                                                  //(4)

    }

    override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {

        touchesEnded(touches, withEvent: event)
    }

    func drawBitmap() {                                                         //(3)

        var rectPath = UIBezierPath()

        UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0.0)
        UIColor.blackColor().setStroke()


        if(firstRun == true) {

            rectPath = UIBezierPath(rect: self.bounds)
            UIColor.whiteColor().setFill()
            rectPath.fill()
            firstRun = false

        }

        incrementalImage.drawAtPoint(CGPointZero)
        path.stroke()
        incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

    }

}

再次感谢您的帮助,我希望这对其他人也有用。

1 个答案:

答案 0 :(得分:0)

几年前我写了一些关于使用Objective-C在iOS中绘图的教程。您可以将此代码转换为Swift而不会出现太多问题。

第一篇文章讨论了每当用户从屏幕上抬起手指时如何将屏幕上的图形上下文渲染到图像中,这样您就可以从该点开始从新的路径生成新路径并将其绘制在那个图像的顶部。

Smooth Freehand Drawing on iOS

第二个具有基于GCD的实现,允许您在后台执行此渲染。

Advanced Freehand Drawing Techniques