Search code examples
arraysobjective-cswiftnsarraystdvector

Most efficient way of getting large std::vector into Swift?


I have an Objective-C class that populates a std:vector with millions of points. The structure of the vector is:

typedef std::vector<CGPoint> CGContour;
typedef std::vector<CGContour> CGContours;

So a CGContour is a vector of CGPoints and CGContours is a vector of the CGContour vector.

I need to access this data in a Swift class somehow. I don't want to use an NSArray because it has a huge overhead compared to using vector (it is like 10x as big and slow).

What would be the most efficient way to get millions of CGPoints accessible in Swift from my Objective-C class?

Edit:

I am populating my CGContours vector like this:

contourVector = CGContours(contours.size());
populatedContourNum = 0


//contours is OpenCV's contours
for( long c = 0; c < contours.size();  c++) {

    if (populatedContourNum >= contourVector.size()) {
        contourVector.resize(contourVector.size() + 1);
    }

    contourVector[populatedContourNum] = CGContour(contours[c].size());

    for( long pointNum = 0; pointNum < contours[c].size(); pointNum++ )
    {
        contourVector[populatedContourNum][pointNum] = CGPointMake(contours[c][pointNum].x * scale,
                                                                         contours[c][pointNum].y * scale);
    }

    populatedContourNum++;

}

Solution

  • Some parts are not clear enough but I will try to show you some example.

    First of all, you need to prepare a class which can access your contourVector. (I cannot see if it is an instance field or a global variable, if it is an instance field, you may use the existing class.)


    Create a header for the prepared class, again you may utilize the existing header, but this header needs to be compiled both in C-context and in C++ context. So, if your existing header contains some declaration which cannot be compiled in C-context, you may need separated two headers or some #ifs.

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface YourClass : NSObject
    
    - (NSInteger)contoursSize;
    - (NSInteger)contourSizeAtIndex:(NSInteger)index;
    - (CGPoint *)contourAtIndex:(NSInteger)index;
    
    //...
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    Then add 3 methods to the class specified in the header:

    #import "YourClass.h"
    #import <vector>
    
    typedef std::vector<CGPoint> CGContour;
    typedef std::vector<CGContour> CGContours;
    
    static CGContours contourVector;
    
    @implementation YourClass
    
    - (NSInteger)contoursSize {
        return contourVector.size();
    }
    - (NSInteger)contourSizeAtIndex:(NSInteger)index {
        return contourVector[index].size();
    }
    - (CGPoint *)contourAtIndex:(NSInteger)index {
        return contourVector[index].data();
    }
    
    @end
    

    Please do not forget to include the header inside your Project-Bridging-Header.h:

    //
    //  Use this file to import your target's public headers that you would like to expose to Swift.
    //
    
    #import "YourClass.h"
    

    You need to create a Swift side wrapper class, as you cannot create UnsafeBufferPointer in Objective-C.

    class YourClassWrapper {
        let yourInstance = YourClass()
    
        var count: Int {
            return yourInstance.contoursSize()
        }
    
        subscript(index: Int) -> UnsafeBufferPointer<CGPoint> {
            guard 0..<count ~= index else {fatalError("Index \(index) out of bounds \(0..<count)")}
            let start = yourInstance.contour(at: index)
            let count = yourInstance.contourSize(at: index)
            return UnsafeBufferPointer(start: start, count: count)
        }
    }
    

    With these preparations above, you can access each CGPoint as:

    let wrapper = YourClassWrapper()
    let point = wrapper[0][1]
    

    Or you can get the pointer to the first element in CGContour as:

    let ptr = wrapper[0].baseAddress!
    

    You may need to modify some parts to fit this into your actual code. Hope you can make it.