20

How can I interact with functions in swift that used to take sized C arrays?

I read through Interacting with C APIS and still can't figure this out.

The documentation for the coords parameter of func getCoordinates(_ coords:UnsafeMutablePointer<CLLocationCoordinate2D>,range range: NSRange) states: "On input, you must provide a C array of structures large enough to hold the desired number of coordinates. On output, this structure contains the requested coordinate data."

I tried several things, most recently:

var coordinates: UnsafeMutablePointer<CLLocationCoordinate2D> = nil
polyline.getCoordinates(&coordinates, range: NSMakeRange(0, polyline.pointCount))

Would I have to use something like:

var coordinates = UnsafeMutablePointer<CLLocationCoordinate2D>(calloc(1, UInt(polyline.pointCount)))

Pulling my hair out here... any thoughts?

Peacemoon
  • 3,198
  • 4
  • 32
  • 56
Andrew Robinson
  • 3,404
  • 3
  • 20
  • 26
  • Did you try: `var coordinates = UnsafeMutablePointer()`? – Mike S Aug 29 '14 at 02:21
  • Sadly, yes. Results in: `'UnsafeMutablePointer' is not identical to 'CLLocationCoordinate2D'` Which really doesn't make any sense to me since it should be `UnsafeMutablePointer'` in the function as well. – Andrew Robinson Aug 29 '14 at 03:04
  • I don't get a compiler error with `var coordinates = [CLLocationCoordinate2D]()`, but the `getCoordinates` function returns nothing to `&coordinates` – Andrew Robinson Aug 29 '14 at 03:07

2 Answers2

51

Normally you can just pass an array of the required type as an in-out parameter, aka

var coords: [CLLocationCoordinate2D] = []
polyline.getCoordinates(&coords, range: NSMakeRange(0, polyline.pointCount))

but that documentation makes it seem like a bad idea! Luckily, UnsafeMutablePointer provides a static alloc(num: Int) method, so you can call getCoordinates() like this:

var coordsPointer = UnsafeMutablePointer<CLLocationCoordinate2D>.alloc(polyline.pointCount)
polyline.getCoordinates(coordsPointer, range: NSMakeRange(0, polyline.pointCount))

To get the actual CLLocationCoordinate2D objects out of the mutable pointer, you should be able to just loop through:

var coords: [CLLocationCoordinate2D] = []
for i in 0..<polyline.pointCount {
    coords.append(coordsPointer[i])
}

And since you don't want a memory leak, finish up like so:

coordsPointer.dealloc(polyline.pointCount)

Just remembered Array has a reserveCapacity() instance method, so a much simpler (and probably safer) version of this would be:

var coords: [CLLocationCoordinate2D] = []
coords.reserveCapacity(polyline.pointCount)
polyline.getCoordinates(&coords, range: NSMakeRange(0, polyline.pointCount))
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
  • 12
    Who are you and why aren't we best friends. – Andrew Robinson Aug 29 '14 at 03:46
  • 14
    Hey I'm here and everything but you never call. – Nate Cook Aug 29 '14 at 03:48
  • 1
    I do have one more question: WHY would the API continue to use `UnsafeMutablePointer` instead of a good ol' optimized swift Array? Seems like a lot of additional effort for nothing. – Andrew Robinson Aug 29 '14 at 05:28
  • 4
    That array is only a bad idea because it is not big enough. If you had created an array of sufficient size, e.g. `var coords: [CLLocationCoordinate2D] = [CLLocationCoordinate2D](count: polyline.pointCount, repeatedValue: kCLLocationCoordinate2DInvalid)` then I don't see why you shouldn't be able to pass `&coords` as the pointer directly. – newacct Aug 29 '14 at 06:34
  • @newacct Understood, so when I was getting no result with `var coordinates = [CLLocationCoordinate2D]()` it was because it needed a specified size like the old C array... duh. My question stands however, why does the API require a sized array? Is it for efficiency? Some weird need for backwards compatibility that I'm not aware of? I understand modern languages sometimes need to have interactions with parent-language elements, but still curious. – Andrew Robinson Aug 29 '14 at 18:05
  • Also, dropping the type declaration is fine too: `var coords = [CLLocationCoordinate2D](count: polyline.pointCount, repeatedValue: kCLLocationCoordinate2DInvalid)` – Andrew Robinson Aug 29 '14 at 18:08
  • 2
    @AndrewRobinson: The API is imported from Objective-C, and types are translated in an automated way. The Objective-C signature is `- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range`. In C, you cannot pass an array, only a pointer (plus, C is pass-by-value, so it wouldn't work anyway because we need the function to be able to modify the array). And a pointer doesn't have an associated size. Plus in C you cannot resize an array already allocated. In Cocoa there is NSMutableArray, but that requires elements that are objects, which CLLocationCoordinate2D is not. – newacct Aug 29 '14 at 18:26
  • Using directly the swift array and reserveCapacity I get an array :( – Matthieu Riegler Jan 28 '15 at 23:58
  • 2
    it will be also nice to use `defer` for the `dealloc`: `var startCoordinates: UnsafeMutablePointer = UnsafeMutablePointer.alloc(step.polyline.pointCount) defer { startCoordinates.dealloc(step.polyline.pointCount) }` – Ben Nov 20 '15 at 17:39
  • @Ben Should I do something like `coordsPointer.dealloc(polyline.pointCount)` and then `free(coordsPointer)` in the `deinit()` func? – Zigii Wong Apr 12 '16 at 02:49
0

extension wrapper for @Nate Cook's awesome answer, cannot get the reserveCapacity() version to work, it keep returning empty object.

import MapKit

extension MKPolyline {

    var coordinates: [CLLocationCoordinate2D] {
        get {
            let coordsPointer = UnsafeMutablePointer<CLLocationCoordinate2D>.allocate(capacity: pointCount)
            var coords: [CLLocationCoordinate2D] = []
            for i in 0..<pointCount {
                coords.append(coordsPointer[i])
            }
            coordsPointer.deallocate(capacity: pointCount)
            return coords
        }
    }
}
DazChong
  • 3,513
  • 27
  • 23