我正在尝试CGPattern在Swift中使用彩色图案。Apple在Quartz 2D编程指南的“ 绘画彩色图案”部分中提供了一个不错的Objective- C示例。但是,从Objective-C转换所有语法并非易事。另外,我想info在绘图回调中使用该参数,并且没有这样做的示例。
CGPattern
info
这是我的第一次尝试:
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,我需要将其self作为info参数传递给CGPattern初始化程序。
drawPattern
CGPatternCallbacks
self
完成此操作的正确语法是什么?
正如您在回答中所说,CGPatternDrawPatternCallback定义为:
CGPatternDrawPatternCallback
typealias CGPatternDrawPatternCallback = @convention(c) (UnsafeMutableRawPointer?, CGContext) -> Void
该@convention(c)属性(似乎只显示在生成的标头中)意味着所使用的函数值必须与C兼容,因此不能捕获任何上下文(因为C函数值只不过是指向函数的原始指针,所以不要t存储其他上下文对象)。
@convention(c)
因此,如果您想在函数中使用上下文,则需要将自己的上下文传递UnsafeMutableRawPointer?给Initializer的info:参数。然后在调用时将其作为给定绘制模式函数的第一个参数传递。CGPattern
UnsafeMutableRawPointer?
info:
为了传递self给该参数,您可以使用Unmanaged。这使您可以在引用和不透明指针之间进行转换,并且与不同unsafeBitCast,它还可以让您控制引用的内存管理。
Unmanaged
unsafeBitCast
鉴于我们不能保证of的调用者createPattern()会self保留,我们 不能 仅将其传递给info:参数而不自己保留它。如果传递时没有保留(例如,使用unsafeBitCast),然后在绘制图案之前将其释放- 尝试在绘图回调中使用悬空指针时,您将得到 未定义的行为 。
createPattern()
与Unmanaged:
您可以将引用作为+1保留的不透明指针传递给 passRetained(_:).toOpaque()
passRetained(_:).toOpaque()
您可以使用以下命令从此指针获取引用fromOpaque(_:).takeUnretainedValue()(实例将保持不变)
fromOpaque(_:).takeUnretainedValue()
然后,您可以通过使用+1保留fromOpaque(_:).release()。CGPattern释放时,您需要执行此操作。
fromOpaque(_:).release()
例如:
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:参数:
SomeShape
struct
Context
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) } }
现在,这也可以解决任何保留周期问题。