How can I convert NSArray<NSInteger> or NSData to UnsafeMutablePointer<UInt8> and back in Swift?

Viewed 162

I'd like to convert NSArray<NSInteger> or NSData to UnsafeMutablePointer<UInt8> for use with calling a native library. The native method in C is exposed as

typedef struct {
    int64_t len;
    uint8_t * _Nullable data;
} ByteBuffer;

int32_t pack(ByteBuffer request, ByteBuffer * _Nullable response, ExternError * _Nullable err);

This becomes in Swift

func pack(_ request: ByteBuffer, _ response: UnsafeMutablePointer<ByteBuffer>?, _ err: UnsafeMutablePointer<ExternError>?) -> Int32

// ByteBuffer
struct ByteBuffer {
    var len: Int64
    var data: UnsafeMutablePointer<UInt8>?
}

This method would be called from React Native, so my swift definition for the input method is

func pack(request: Array<NSInteger>, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void { }

I'm not making much progress, as I struggle understanding how swift works with pointers.

    func pack(request: Array<NSInteger>, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {

        var req = ByteBuffer(len: Int64(request.count), data: nil)
        req.data = UnsafeMutablePointer<UInt8>.allocate(capacity: request.count)
        
        let ptr = UnsafePointer<UInt8>([1, 2, 3])
        req.data?.assign(from: ptr, count: request.count)
        
        pack(req, _, _)
    }

I'm not sure if I'm on the right track here, I would appreciate help in any direction. The code above also produces a warning Initialization of 'UnsafePointer<UInt8>' results in a dangling pointer.

Would it be easier to just write this in Objective C instead?

1 Answers

I managed to get past by. Posting a solution here, in case someone else needs to work with passing pointer structures to C from Swift. The trick was to use withUnsafeBytes of NSData type or create UnsafeMutablePointer<T> using OpaquePointer. This allows getting a pointer to the underlying data, without doing a copy. Reading back the ByteBuffer was easy just by calling the corresponding initialized of NSData.

var request = Array<NSInteger>()
request.append(16)
request.append(2)

let requestData = NSMutableData()
for i in request {
    var i = i
    requestData.append(&i, length: 1)
}

let req = ByteBuffer(len: Int64(requestData.count), data: UnsafeMutablePointer<UInt8>(OpaquePointer(requestData.bytes)))

// If working with NSData, instead NSMutableData, the following code can be used
let req = ByteBuffer(len: Int64(requestData.count),
                     data: requestData.withUnsafeBytes { body in UnsafeMutablePointer<UInt8>(mutating: body) })

var res = ByteBuffer()
var err = ExternError()

let code = didcomm_generate_key(req, &res, &err)

if code == 0 {
    // Response can be obtained easily using NSData ctor
    let responseData = NSData(bytesNoCopy: res.data!, length: Int(res.len), freeWhenDone: true)
}
Related