问题
I'm making an app where I want to draw a lot of shapes - circles, boxes, lines, etc. Millions of them.
To test the performance of this, I threw together this simple UIView. Note that credit is due - I got inspired by this project.
import UIKit
let qkeyString = "label" as NSString
var QKEY = qkeyString.UTF8String
let qvalString = "com.hanssjunnesson.Draw" as NSString
var QVAL = qvalString.UTF8String
public class RenderImageView: UIView {
var bitmapContext: CGContext?
let drawQueue: dispatch_queue_attr_t = {
let q = dispatch_queue_create(QVAL, nil)
dispatch_queue_set_specific(q, QKEY, &QVAL, nil)
return q
}()
public override init() {
super.init()
render()
}
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
render()
}
override required public init(frame: CGRect) {
super.init(frame: frame)
render()
}
public override func drawRect(rect: CGRect) {
if let bitmapContext = self.bitmapContext {
let context = UIGraphicsGetCurrentContext()
let image = CGBitmapContextCreateImage(bitmapContext)
CGContextDrawImage(context, self.bounds, image)
}
}
private func render() {
dispatch_async(drawQueue) {
let startDate = NSDate()
let bounds = self.bounds
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
let context = UIGraphicsGetCurrentContext()
self.bitmapContext = context
CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor)
CGContextFillRect(context, bounds)
CGContextSetFillColorWithColor(context, UIColor(red: 0.15, green: 0.4, blue: 0.8, alpha: 1.0).CGColor)
for i in 1...1000000 {
CGContextFillEllipseInRect(context, bounds)
}
UIGraphicsEndImageContext()
self.setNeedsDisplay()
let benchmark = startDate.timeIntervalSinceNow
println("Rendering took: \(-benchmark*1000) Ms")
}
}
}
This works just fine. On my iOS simulator, it takes little over a minute to draw a million circles on top of each other.
I wanted to speed this up, so I tried drawing to the bitmap context from multiple threads.
let group = dispatch_group_create()
for i in 1...100 {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
dispatch_group_enter(group)
CGContextFillEllipseInRect(context, bounds)
dispatch_group_leave(group)
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
This did not work, however. I get an EXC_BAD_ACCESS
when calling CGContextFillEllipseInRect(context, bounds)
.
Drawing to a CGContext
in a background thread seems fine as long as it's the same thread that created it.
Anyone know of a way of getting this to work?
回答1:
1) You aren't actually waiting for the group you created to finish -- dispatch_group_wait
is going to be called in that code before any of the blocks have been executed, so the enter / leave calls inside them won't have any effect. Use dispatch_group_async
instead (see below).
2) You can't draw to a CGContext
from two different threads at the same time -- you can see this if you add a println inside your drawing loop. It will work a few times, with varying results, but eventually you'll be end up with an error.
let group = dispatch_group_create()
for i in 1...10 {
dispatch_group_async(group, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
for j in 1...100 {
println("i:\(i), j:\(j)")
CGContextFillEllipseInRect(context, bounds)
}
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
Sample output:
iiii::::4123,,,, jjjj::::1111
ii:1, j:2
:5, j:1
i:6, j:1
EXC_BAD_ACCESS
The only solution to this is to jump back on a single thread for drawing, but that defeats what you were trying to do any way. If you have to do lots of calculations to decide what to draw, that could happen on separate threads, but drawing to CGContext
itself isn't thread safe.
来源:https://stackoverflow.com/questions/27064119/can-i-draw-to-the-same-cgcontextref-from-multiple-threads