在Swift3中使用CGPattern回调时遇到麻烦

问题描述:

我试图在Swift中使用CGPattern创建一个彩色模式。 Apple在其Painting Colored Patterns的部分中的Quartz 2D Programming Guide中提供了一个很好的Objective-C示例。但是从Objective-C转换所有的语法并不是直截了当的。另外我想在图纸回调中使用info参数,并且没有这样做的例子。在Swift3中使用CGPattern回调时遇到麻烦

这是我第一次尝试:

class SomeShape { 
    func createPattern() -> CGPattern? { 
     let bounds = CGRect(x: 0, y: 0, width: someWidth, height: someHeight) 
     let matrix = CGAffineTransform.identity 
     var callbacks = CGPatternCallbacks(version: 0, drawPattern: nil, releaseInfo: nil) 

     let res = CGPattern(info: nil, bounds: bounds, matrix: matrix, xStep: bounds.width, yStep: bounds.height, tiling: .noDistortion, isColored: true, callbacks: &callbacks) 

     return res 
    } 
} 

显然,这需要为drawPattern参数CGPatternCallbacks一个合适的值,我需要通过selfinfo参数为CGPattern初始化。

完成此操作的正确语法是什么?

如你说in your answerCGPatternDrawPatternCallback定义为:

typealias CGPatternDrawPatternCallback = 
           @convention(c) (UnsafeMutableRawPointer?, CGContext) -> Void 

@convention(c)属性(其仅出现在生成的头会显示)意味着使用的函数值必须是兼容C,因此无法捕获任何上下文(因为C函数值只不过是指向函数的原始指针,并且不存储额外的上下文对象)。

因此,如果要在功能中使用上下文,则需要将自己的UnsafeMutableRawPointer?传递给参数CGPattern's initialiserinfo:。这将作为被调用的给定绘图模式函数的第一个参数传递。

为了将self传递给此参数,您可以使用Unmanaged。这允许您在引用和不透明指针之间进行转换,并且与unsafeBitCast不同,它还允许您在执行此操作时控制引用的内存管理。

既然我们都难保的createPattern()主叫方将继续保留self,我们不能只是将其传递到info:参数不保留它自己。如果通过时没有保留(例如使用unsafeBitCast),然后在绘制模式之前解除分配 - 尝试在绘图回调中使用悬挂指针时,您会得到未定义的行为

随着Unmanaged

  • 您可以通过参考作为保留+1不透明指针与passRetained(_:).toOpaque()

  • 你可以拿回这个指针与fromOpaque(_:).takeUnretainedValue()参考(和实例将保持保留)

  • 然后,您可以使用fromOpaque(_:).release()消费+1保留。当CGPattern被释放时,你会想要这样做。

例如:

class SomeShape { 
    // the bounds of the shape to draw 
    let bounds = CGRect(x: 0, y: 0, width: 40, height: 40) 

    func createPattern() -> CGPattern? { 

     var callbacks = CGPatternCallbacks(version: 0, drawPattern: { info, ctx in 

      // cast the opaque pointer back to a SomeShape reference. 
      let shape = Unmanaged<SomeShape>.fromOpaque(info!).takeUnretainedValue() 

      // The code to draw a single tile of the pattern into "ctx"... 
      // (in this case, two vertical strips) 
      ctx.saveGState() 
      ctx.setFillColor(UIColor.red.cgColor) 
      ctx.fill(CGRect(x: 0, y: 0, 
          width: shape.bounds.width/2, height: shape.bounds.height)) 

      ctx.setFillColor(UIColor.blue.cgColor) 
      ctx.fill(CGRect(x: 20, y: 0, 
          width: shape.bounds.width/2, height: shape.bounds.height)) 
      ctx.restoreGState() 

     }, releaseInfo: { info in 
      // when the CGPattern is freed, release the info reference, 
      // consuming the +1 retain when we originally passed it to the CGPattern. 
      Unmanaged<SomeShape>.fromOpaque(info!).release() 
     }) 

     // retain self before passing it off to the info: parameter as an opaque pointer. 
     let unsafeSelf = Unmanaged.passRetained(self).toOpaque() 

     return CGPattern(info: unsafeSelf, bounds: bounds, matrix: .identity, 
         xStep: bounds.width, yStep: bounds.height, 
         tiling: .noDistortion, isColored: true, callbacks: &callbacks) 
    } 
} 

另外,如果你想为SomeShape值语义更好的解决方案,你可以让它变成一个struct。然后创建模式时,你可以把它包装起来的Context堆分配箱其传递到info:参数之前:

struct SomeShape { 

    // the bounds of the shape to draw 
    let bounds = CGRect(x: 0, y: 0, width: 40, height: 40) 

    func createPattern() -> CGPattern? { 

     final class Context { 
      let shape: SomeShape 
      init(_ shape: SomeShape) { self.shape = shape } 
     } 

     var callbacks = CGPatternCallbacks(version: 0, drawPattern: { info, ctx in 

      // cast the opaque pointer back to a Context reference, 
      // and get the wrapped shape instance. 
      let shape = Unmanaged<Context>.fromOpaque(info!).takeUnretainedValue().shape 

      // ... 

     }, releaseInfo: { info in 
      // when the CGPattern is freed, release the info reference, 
      // consuming the +1 retain when we originally passed it to the CGPattern. 
      Unmanaged<Context>.fromOpaque(info!).release() 
     }) 

     // wrap self in our Context box before passing it off to the info: parameter as a 
     // +1 retained opaque pointer. 
     let unsafeSelf = Unmanaged.passRetained(Context(self)).toOpaque() 

     return CGPattern(info: unsafeSelf, bounds: bounds, matrix: .identity, 
         xStep: bounds.width, yStep: bounds.height, 
         tiling: .noDistortion, isColored: true, callbacks: &callbacks) 
    } 
} 

现在,这也需要的任何保留周期的担忧护理。

+0

感谢您的信息。这是我在Swift中处理“指针”的第一个。在搜索时我遇到了'unsafeBitCast'的使用。它适用于我的情况,但很高兴知道有更好的方法。在我真正的代码中,我实际上是将'CALayer'的实例作为'info'参数传递。这是'createPattern'方法内创建的局部变量。它在我的代码中使用'unsafeBitCast'工作得很好。包装“CALayer”类型的局部变量的更安全的方法是什么? – rmaddy

+0

@rmaddy我假设你将'CALayer'实例局部变量添加到图层层次结构中,然后保留它(或其他一些东西保留它)?这是我能想到为什么它会使用'unsafeBitCast'的唯一方法。除非存在保留周期问题(即'CALayer'实例保留对'CGPattern'的引用),否则我仍然建议在将'CALayer'实例保留为'Unmanaged'(与第一个示例中的完全相同)之前,将它传递给'info:'参数 - 只是防止它在绘制模式之前被释放。 – Hamish

+0

如果存在保留周期问题,您可以随时创建一个'Weak'类包装器,该包装器对给定图层持有一个弱引用,然后传递保留的包装器。然而,当然,如果给定的'CALayer'总是给定'CGPattern'的唯一拥有者,那么你不应该做任何额外的保留(有效地使它成为“无主”)。 – Hamish

我们先看CGPatternDrawPatternCallback。它被定义为:

typealias CGPatternDrawPatternCallback = (UnsafeMutableRawPointer?, CGContext) -> Void 

所以这是一个封闭,它有两个参数 - info和绘图上下文。

有了这些信息,你可以创建CGPatternCallback如下:

var callbacks = CGPatternCallbacks(version: 0, drawPattern: { (info, ctx) in 
    // Drawing code here 
}, releaseInfo: { (info) in { 
    // Cleanup code here 
}) 

但还有这里需要注意一些重要的事情。这些关闭的身体无法捕捉任何东西。如果试图这样做,你会得到以下错误:

A C function point cannot be formed from a closure that captures context

这就是为什么info参数需要使用。在创建图案时,您可以传递self或其他一些对象作为info参数,并在绘图回调中使用该参数。但是这不是一个简单的任务,因为您不能简单地通过self作为info参数。您需要将其转换为所需的UnsafeMutableRawPointer,然后将其从图形回调中的指针转换回来。

下面是与所有设置的完整代码:

class SomeShape { 
    func createPattern() -> CGPattern? { 
     let bounds = CGRect(x: 0, y: 0, width: someWidth, height: someHeight) // The size of each tile in the pattern 
     let matrix = CGAffineTransform.identity // adjust as needed 
     var callbacks = CGPatternCallbacks(version: 0, drawPattern: { (info, ctx) in 
      let shape = unsafeBitCast(info, to: SomeShape.self) 

      // The needed drawing code to draw one tile of the pattern into "ctx" 
     }, releaseInfo: { (info) in 
      // Any cleanup if needed 
     }) 

     let unsafeSelf = unsafeBitCast(self, to: UnsafeMutableRawPointer.self) 

     let res = CGPattern(info: unsafeSelf, bounds: bounds, matrix: matrix, xStep: bounds.width, yStep: bounds.height, tiling: .noDistortion, isColored: true, callbacks: &callbacks) 

     return res 
    } 
} 

,并利用该CGPattern,你可以做这样的事情:

func draw(_ ctx: CGContext) { 
    // Any other needed setup 

    let path = CGPath(....) // some path 

    // Code to fill a path with the pattern 
    ctx.saveGState() 
    ctx.addPath(path) // The path to fill 

    // Setup the pattern color space for the colored pattern 
    if let cs = CGColorSpace(patternBaseSpace: nil) { 
     ctx.setFillColorSpace(cs) 
    } 

    // Create and apply the pattern and its opacity 
    if let fillPattern = someShapeInstance.createPattern() { 
     var fillOpacity = CGFloat(1.0) 
     ctx.setFillPattern(fillPattern, colorComponents: &strokeOpacity) 
    } 

    ctx.fillPath(using: theDesiredFillRule) 
    ctx.restoreGState() 

    // Any other drawing 
} 

当使用彩色模式(与一个模板,非彩色图案),您必须在设置填充图案之前设置填充颜色空间。

您还可以使用模式来描边路径。只需使用setStrokeColorSpacesetStrokePattern即可。

+0

maddy,你有没有想法,为什么苹果声明CGPatternDrawPatternCallback第一个参数是指针的可变版本? – user3441734

+0

@ user3441734在Objective-C中'info'是一个普通的'void *'。我真的没有以前的经验,在Swift中不安全[Mutable] [Raw]指针,所以我不能肯定地说。但我的猜测是,通过将其设置为'UnsafeMutableRawPointer'而不是'UnsafeRawPointer',它可以让您更多地使用'info'参数以及如何使用它。如果还没有,我建议在这个主题上发布一个单独的问题。 – rmaddy

+0

值得注意的是,这里有两个未定义行为的主要机会。首先,如果在绘制模式之前释放了'self',则在尝试在绘图回调中使用悬挂指针时会出现未定义的行为。其次(更重要的是,因为你有控制权),如果你将'nil'传递给'info:'参数,当你尝试使用'nil'作为非可选参考时,你会得到未定义的行为。使用'Unmanaged',如[在我的答案](https://stackoverflow.com/a/44215903/2976878)中所示,在参考和不透明指针之间进行转换可以防止这两种情况。 – Hamish