2

I am trying to convert this piece of code to Swift:

NSData *testData = [@"Whatever" dataUsingEncoding:NSUTF8StringEncoding];
    void (^whatever)(NSOutputStream *) = ^(NSOutputStream *stream) {
        [stream open];
        NSRange myRange = {0};
        while (myRange.location < testData.length) {
            myRange.location += myRange.length;
            myRange.length = 4096;

            if (myRange.location + myRange.length > testData.length) {
                myRange.length = testData.length - myRange.location;
            }

            [stream write:&(testData.bytes[myRange.location])
                       maxLength:myRange.length];
        }
        [stream close];
    };

Unfortunately, i am really stuck on &(testData.bytes[myRange.location]).

The swift converters i found online don't process that part at all, and Swift compiler complains that:

Value of type 'UnsafeRawPointer' has no subscripts

The examples with .withUnsafeBytes don't show how to get the bytes pointer at a specific location.

Here's the swift:

        let testData = "Whatever".data(using: .utf8)!
        let whatever: ((OutputStream) -> Void)? = { stream in
            stream.open()
            var myRange = NSRange()
            while myRange.location < testData.count {
                myRange.location += myRange.length
                myRange.length = 4096

                if myRange.location + myRange.length > testData.count {
                    myRange.length = testData.count - myRange.location
                }
                // the next line doesn't work
                stream.write(testData.bytes[myRange.location], maxLength: myRange.length)
            }
            stream.close()
        }
zaitsman
  • 8,984
  • 6
  • 47
  • 79
  • Have a look at [Writing Data to an NSOutputStream in Swift 3](https://stackoverflow.com/q/38090320/1187415) and in particular the Swift 5 answer. – Martin R Oct 01 '19 at 06:53
  • @MartinR did you mean this? https://stackoverflow.com/a/46301838/2057955 That takes the entire data in one go, not chunked – zaitsman Oct 01 '19 at 06:55

2 Answers2

5

Objective-C is a superset of C, where taking the address of a subscripted pointer is equivalent to adding an offset to the pointer:

&pointer[index] == pointer + index

In your case,

[stream write:&(testData.bytes[myRange.location]) maxLength:myRange.length];

is equivalent to

[stream write:testData.bytes + myRange.location maxLength:myRange.length];

This shows that the problem is not really about subscripting, but about how to obtain a pointer to the element storage of a Data value at an offset.

Obtaining a pointer to the element storage is done using withUnsafeBytes() and binding the raw pointer to an UInt8 pointer (see also Writing Data to an NSOutputStream in Swift 3). This can be combined with slicing (testData[offset...]) to write the data at a specified offset:

let testData = "Whatever".data(using: .utf8)!
let whatever: ((OutputStream) -> Void)? = { stream in
    stream.open()
    var position = 0
    while position < testData.count {
        let length = min(4096, testData.count - position)
        let amount =  testData[position...].withUnsafeBytes {
            stream.write($0.bindMemory(to: UInt8.self).baseAddress!, maxLength: length)
        }
        if amount <= 0 {
            // Error or EOF
            break
        }
        position += amount
    }
    stream.close()
}

Note that the write method returns the number of bytes actually written (or 0 or -1). When writing to anything but a plain file (e.g. a TCP socket, a pipe, ...) this can be less than the value of the maxLength parameter.

Alternatively you can obtain the pointer to the element storage once, and then increment it by the amount of data written:

let testData = "Whatever".data(using: .utf8)!
let whatever: ((OutputStream) -> Void)? = { stream in
    stream.open()
    var length = testData.count
    testData.withUnsafeBytes {
        var ptr = $0.bindMemory(to: UInt8.self).baseAddress!
        while length > 0 {
            let amount = stream.write(ptr, maxLength: min(length, 4096))
            if amount <= 0 { break }
            ptr += amount
            length -= amount
        }
    }
    stream.close()
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
1

Delete bytes and subscript the Data object directly.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • That gives me `Cannot convert value of type 'UInt8' to expected argument type 'UnsafePointer'` – zaitsman Oct 01 '19 at 05:52
  • Gives it to you where? You have shown no Swift code at all! You asked how to get the byte at a specific location and that is how. – matt Oct 01 '19 at 05:55
  • On the same line... `stream.write(testData[myRange.location], maxLength: myRange.length)` – zaitsman Oct 01 '19 at 06:00