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

标签 ios multithreading swift bitmap drawrect

感谢您花时间阅读我的主题。最近我一直在开发一个简单的 iOS 自由绘制应用程序并且遇到了一个目前超出我的技能水平来解决的问题。几天来我一直在网上搜索以尝试找到解决方案,但到目前为止还没有成功。幸运的是,我已经想到了解决我的应用程序延迟问题的方法,但是我仍然需要帮助,因为我真的不知道如何实现它。

程序这部分如何运作的简要说明:

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

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() 进行了试验,发现填充线数组只相当于总 CPU 需求的 10%。据我了解,这是因为 drawRect 中的任何内容都未应用于 lines 数组中的坐标。

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

这意味着通过强制 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()

    }

}

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

最佳答案

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

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

Smooth Freehand Drawing on iOS

第二个具有基于 GCD 的实现,可让您在后台进行此渲染。

Advanced Freehand Drawing Techniques

关于ios - 有关 Swift 上 iOS 中 drawRect 的性能问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28574523/

相关文章:

multithreading - 如何仅在perl中按需启动线程?

ios - 如何在 iOS 模拟器中获取虚假联系人?

ios - 修复 : . 中的错误重复符号 _response ...:链接器命令失败,退出代码为 1(使用 -v 查看调用)

ios - 在 iOS 上,为什么 ViewController 中的 init 不起作用?

ios - Swift - 如何将保存的音频文件对话转换为文本?

arrays - 分配数据添加到 [String :[AnyObject]]() not order by

c# - 实现多线程后与数据库的连接丢失

android - 在 Handler 和 Thread Widget 中设置 TextView

ios - 尝试使用警报完成刷新 Collection View

swift - 无法访问全局常量文件中定义的枚举案例的原始值