0

I’m playing around with learning about pointers in Swift.

For instance, this code starts with a CGPoint array, creates an UnsafePointer and then extracts all the x values into a CGFloat array:

import Foundation

let points = [CGPoint(x:1.2, y:3.33), CGPoint(x:1.5, y:1.21), CGPoint(x:1.48, y:3.97)]
print(points)

let ptr = UnsafePointer(points)
print(ptr)

func xValues(buffer: UnsafePointer<CGPoint>, count: Int) -> [CGFloat]? {
    return UnsafeBufferPointer(start: buffer, count: count).map { $0.x }
}

let x = xValues(buffer: ptr, count: points.count)
print(x)

And the expected output is:

[Foundation.CGPoint(x: 1.2, y: 3.33), Foundation.CGPoint(x: 1.5, y: 1.21), Foundation.CGPoint(x: 1.48, y: 3.97)]
0x0000556d6b818aa0
Optional([1.2, 1.5, 1.48])

Now I’d like to have the xValues function return directly UnsafePointer<CGFloat>, instead of going through [CGFloat].

How do I do that, is that possible?

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
koen
  • 5,383
  • 7
  • 50
  • 89
  • 1
    Well, for one thing, never say `let ptr = UnsafePointer(points)`. You're not getting a warning when you do that? Update to Xcode 11.4 immediately so that you do. It's deprecated now and will be illegal in future. – matt Apr 14 '20 at 13:19
  • I am actually on a PC right now and was using an online playground website to test (http://online.swiftplayground.run/). And got no warning. What should it be instead? – koen Apr 14 '20 at 13:21
  • Also, my Mac is too old to run Xcode 11. – koen Apr 14 '20 at 13:22
  • 2
    Depends what you want to do. You might say `points.withUnsafeBufferPointer`. – matt Apr 14 '20 at 13:23
  • Fine but the code is still wrong. Assigning / passing an unsafe pointer is (wait for it...) unsafe. – matt Apr 14 '20 at 13:23
  • Thanks for (wait for it...) pointing that out. So where exactly should I use `points.withUnsafeBufferPointer`? – koen Apr 14 '20 at 13:29
  • Also, the map function rethrows error and returns a [T] not [T]?. https://developer.apple.com/documentation/swift/unsafebufferpointer/3019132-map So you should catch the potentiel errors. Then you can get the UnsafePointer for the new array the same way you would for the original one. – La pieuvre Apr 14 '20 at 13:30

1 Answers1

2

It is unsafe to output pointers like that. As mentioned in comments you should use withUnsafeBufferPointer method to access the underlying buffer:

let points = [
  CGPoint(x:1.2, y:3.33), 
  CGPoint(x:1.5, y:1.21), 
  CGPoint(x:1.48, y:3.97)
]

let xValues = points.withUnsafeBufferPointer { buffer in
  return buffer.map { $0.x }
}

If you need a pointer to the array of CGFloat just use the same method as above:

xValues.withUnsafeBufferPointer { buffer in 
  // Do things with UnsafeBufferPointer<CGFloat>
}

A good Swift Pointer tutorial here.


Edit

Here is a working example:

let points = [
    CGPoint(x:1.2, y:3.33), 
    CGPoint(x:1.5, y:1.21),
    CGPoint(x:1.48, y:3.97)
]

// Create, init and defer dealoc
let ptr = UnsafeMutablePointer<CGFloat>.allocate(capacity: points.count)
ptr.initialize(repeating: 0.0, count: points.count)
defer {
    ptr.deallocate()
}

// Populate pointer
points.withUnsafeBufferPointer { buffer in
    for i in 0..<buffer.count {
        ptr.advanced(by: i).pointee = buffer[i].x
    }
}

// Do things with UnsafeMutablePointer<CGFloat>, for instance:
let buffer = UnsafeBufferPointer(start: ptr, count: points.count)

for (index, value) in buffer.enumerated() {
    print("index: \(index), value: \(value)")
}
Louis Lac
  • 5,298
  • 1
  • 21
  • 36
  • Thank you for explaining how to use `withUnsafeBufferPointer`. So just that I understand completely, to get the pointer to the array of xValues, I always need to create a `CGFloat` array first and then get the pointer for the new array? Or is there a way to do this without using the intermediate `[CGFloat]`? – koen Apr 14 '20 at 14:18
  • 1
    No, you can do any nasty thing you want with pointers, especially with Swift `RawPointers` but it is not recommended. See here for some good tutorial on Swift Pointers https://www.raywenderlich.com/7181017-unsafe-swift-using-pointers-and-interacting-with-c. – Louis Lac Apr 14 '20 at 14:29
  • 1
    You can allocate a BufferPointer of `n` CGFloat and then directly populate it with values in the original array. – Louis Lac Apr 14 '20 at 14:31
  • 1
    Yes, I saw the RW tutorial - very informative but dense. Need to read it again (and again...). Merci! – koen Apr 14 '20 at 16:28
  • Can you explain why you defer deallocation? – koen Apr 14 '20 at 18:26
  • 1
    When using pointers you need to manage memory yourself I.e alloc and dealloc. If you forgot to do this this will cause memory leaks and crashes for bad access if the pointer turns to nil. Defer is just a convenience often used to not forget to execute a block of code before exiting the current scope, it make it clear that you didn’t forget to de allocate here. – Louis Lac Apr 14 '20 at 18:36
  • Reason I ask, if I wrap the create and populate part in a function that returns a `UnsafeMutablePointer`, and then print the resulting ptr as you described in your example, the output is "index: 0, value: 4.6813945063882e-310; index: 1, value: 1.5; index: 2, value: 1.48. If I comment out the `deallocate` line, I get the correct output. – koen Apr 14 '20 at 18:44
  • 1
    Yes because here the problem is invisible but there is a memory leak: the pointer is never deallocated. The values are still accessible but a good program should return with memory deallocated. Try to put the deallocate before printing or just after your function call, you'll see that there is no crash but the values should be nonsensical. This is called undefined behaviour. – Louis Lac Apr 14 '20 at 18:52
  • 1
    In a program where you call your function thousands of times and if you never deallocate memory you will run out of memory and the program will crash. – Louis Lac Apr 14 '20 at 18:53
  • 1
    Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/211662/discussion-between-koen-and-louis-lac). – koen Apr 14 '20 at 19:06