8 Replies
      Latest reply: Sep 14, 2016 1:53 PM by eskimo RSS
      ole Level 1 Level 1 (20 points)

        I’d like to add a property to UIBezierPath that returns an array of the path’s elements. So I need to call the CGPathApply() function, which takes a function pointer (a closure in Swift 2.0) and calls the closure for each element in the path. My problem is: how do I get the data about the path elements out of the callback closure and stored in a Swift array? The closure can’t capture any variables (because it corresponds to a C function pointer), so I think I have to pass a pointer to a Swift array as a parameter. Thankfully, CGPathApply() takes a parameter info: UnsafeMutablePointer<Void> and passes it to the closure.

         

        My problem is in line 10: how do I turn the UnsafeMutablePointer<Void> back into a Swift array that I can operate on inside the closure?

         

        extension UIBezierPath {
            var elements: [CGPathElementType] {
                // I'm trying to reserve enough memory for the array. 
                // I don't know how many elements the path has, but this should be good enough for testing.
        
                var elements = ContiguousArray<CGPathElementType>()
                elements.reserveCapacity(1000)
        
                CGPathApply(self.CGPath, &elements) { (userInfo: UnsafeMutablePointer<Void>, elementPointer: UnsafePointer<CGPathElement>) -> Void in
                    // I don’t know how to convert userInfo back into a Swift array
                    var elements = ???
                    let elementType = elementPointer.memory.type
                    elements.append(elementType)
                }
        
                return Array(elements)
            }
        }
        

         

        I tried var elements = UnsafeMutablePointer<[CGPathElementType]>(userInfo).memory. This compiles but when I run the code on a bezier path with 5 path elements, the resulting array is empty.

        • Re: Populating a Swift array in a C function callback
          Jens Level 3 Level 3 (230 points)

          Isn't the closure supposed to get only one CGPathElement (and it will get called repeatedly, one time for each CGPathElement in the CGPath)?

          • Re: Populating a Swift array in a C function callback
            LCS Level 4 Level 4 (595 points)

            I think the problem is that the array is being copied and becomes a new seperate array when you append something to it. So the original array is still empty, and that's the only reference you have.

             

            I think you would need to pass a pointer to a pointer to an array (so that you can update the passed pointer to hold a pointer to the new updated array each time).

              • Re: Populating a Swift array in a C function callback
                ole Level 1 Level 1 (20 points)

                That’s a good point, thanks. I’ll try something like that and report back here with the result.

                • Re: Populating a Swift array in a C function callback
                  ole Level 1 Level 1 (20 points)

                  I got it to work by wrapping the call to CGPathApply in a withUnsafeMutablePointer() call. To be honest, I’m still not totally sure why this works. The whole UnsafePointer stuff in the standard library and how it interacts with value types is still somewhat unclear to me.

                   

                  Here is the working code:

                   

                  extension UIBezierPath {
                      var elements: [CGPathElementType] {
                          var pathElements = [CGPathElementType]()
                          withUnsafeMutablePointer(&pathElements) { elementsPointer in
                              CGPathApply(CGPath, elementsPointer) { (userInfo, nextElementPointer) in
                                 // The type of the next path element we want to store in the array
                                  let nextElement = nextElementPointer.memory.type
                  
                                  // userInfo is an UnsafeMutablePointer<Void>.
                                 // Cast it back into an UnsafeMutablePointer<[PathElement]>
                                  let elementsPointer = UnsafeMutablePointer<[PathElement]>(userInfo)
                  
                                  // Wrapping everything in withUnsafeMutablePointer() makes this work.
                                 // nextElement is appended to the array that elementsPointer points to.
                                  elementsPointer.memory.append(nextElement)
                              }
                          }
                          return pathElements
                      }
                  }
                  

                   

                  I’d welcome any comments for improvement or clarification what withUnsafeMutablePointer exactly does and why I can now mutate the array inside CGPathApply().

                    • Re: Populating a Swift array in a C function callback
                      eskimo Apple Staff Apple Staff (7,005 points)

                      I was intrigued by your question because I often end up using low-level APIs that have C callbacks.  In Objective-C I typically get around this by writing my own block-based wrapper for the function.  For example:

                       

                      static void MyPathApplierFunction(void * __nullable info, const CGPathElement * element) {
                          CGPathApplierBlock block;
                      
                          block = (__bridge CGPathApplierBlock) info;
                          block(element);
                      }
                      
                      extern void CGPathApplyBlock(CGPathRef __nonnull path, CGPathApplierBlock __nonnull block) {
                          CGPathApply(path, (void *) block, MyPathApplierFunction);
                      }
                      

                       

                      Now that we have C callbacks in Swift, I figured you could do the same.  I came up with two different approaches:

                       

                      typealias CGPathApplierBlockA = (CGPathElement) -> Void
                      
                      func CGPathApplyBlockA(path: CGPath, @noescape block: CGPathApplierBlockA) {
                         var blockBox = Box(block)
                         withUnsafeMutablePointer(&blockBox) { blockBoxPtr in
                             CGPathApply(path, blockBoxPtr, { info, element in
                                 let blockBox2 = UnsafePointer<Box<CGPathApplierBlockA>>(info).memory
                                 blockBox2.value?(element.memory)
                             })
                         }
                         blockBox.value = nil
                      }
                      

                       

                      and:

                       

                      typealias CGPathApplierBlockB = @convention(block) (CGPathElement) -> Void
                      
                      func CGPathApplyBlockB(path: CGPath, block: CGPathApplierBlockB) {
                         CGPathApply(path, unsafeBitCast(block, UnsafeMutablePointer<Void>.self), { info, element in
                             let block2 = unsafeBitCast(info, CGPathApplierBlockB.self)
                             block2(element.memory)
                         })
                      }
                      

                       

                      After talking withs over with the some folks internally I'm going to recommend approach B.  It's a bit scary but it's also the most efficient and the most like the Objective-C version.

                      Share and Enjoy

                      Quinn "The Eskimo!"
                      Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                      let myEmail = "eskimo" + "1" + "@apple.com"

                        • Re: Populating a Swift array in a C function callback
                          ole Level 1 Level 1 (20 points)

                          Thanks for the ideas! I’m not sure I like the unsafeBitCast version but passing a block in the userInfo param is definitely an intriguing idea. I hadn’t thought of that.

                          • Re: Populating a Swift array in a C function callback
                            eskimo Apple Staff Apple Staff (7,005 points)

                            Someone asked me (offline) for a Swift 3 version of this code.  Here it is.

                            typealias CGPathApplierBlock = @convention(block) (CGPathElement) -> Void
                            
                            extension CGPath {
                                func apply(body: CGPathApplierBlock) {
                                    self.apply(info: unsafeBitCast(body, to: UnsafeMutableRawPointer.self)) { (info, element) in
                                        let block2 = unsafeBitCast(info, to: CGPathApplierBlock.self)
                                        block2(element.pointee)
                                    }
                                }
                            }
                            

                            ps We finally got around to filing a bug requesting that CG support this directly (r. 28303751).  That’s cool for this particular case but I find I need this technique in lots of other places where I call low-level APIs.

                            Share and Enjoy

                            Quinn “The Eskimo!”
                            Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                            let myEmail = "eskimo" + "1" + "@apple.com"