3

I want to convert an Int32 to a string consisting of four C-style, 1-byte wide characters (probably closely related to this but in Swift 3).

The use for this is that many API functions of Core Audio return an OSStatus (really an Int32), which can often be interpreted as string consisting of four C-style characters.

fun interpretAsString(possibleMsg: Int32) -> String {
  // Blackbox
}
Community
  • 1
  • 1
  • Possible duplicate of [How in swift to convert Int16 to two UInt8 Bytes](http://stackoverflow.com/questions/32830866/how-in-swift-to-convert-int16-to-two-uint8-bytes) – BallpointBen Mar 03 '17 at 14:28
  • Have you tried `NSFileTypeForHFSTypeCode`? Despite its name, it can convert any 4-char code from `OSType` to `String` – Code Different Mar 03 '17 at 14:43
  • @CodeDifferent: That is not available on iOS. – Martin R Mar 03 '17 at 15:11
  • related: https://stackoverflow.com/questions/31320243/swift-equivalent-to-objective-c-fourcharcode-single-quote-literals-e-g-text – pkamb Aug 20 '19 at 20:48

3 Answers3

4

Actually a "four character code" is usually an unsigned 32-bit value:

public typealias FourCharCode = UInt32
public typealias OSType = FourCharCode

The four bytes (from the MSB to the LSB) each define one character. Here is a simple Swift 3 function to convert the integer to a string, inspired by the various C/Objective-C/Swift 1+2 solutions in iOS/C: Convert "integer" into four character string:

func fourCCToString(_ value: FourCharCode) -> String {
    let utf16 = [
        UInt16((value >> 24) & 0xFF),
        UInt16((value >> 16) & 0xFF),
        UInt16((value >> 8) & 0xFF),
        UInt16((value & 0xFF)) ]
    return String(utf16CodeUnits: utf16, count: 4)
}

Example:

print(fourCCToString(0x48454C4F)) // HELO

I have chosen an array with the UTF-16 code points as intermediate storage because that can directly be used to create a string.

If you really need it for a signed 32-bit integer then you can call

fourCCToString(FourCharCode(bitPattern: i32value)

or define a similar function taking an Int32 parameter.

As Tim Vermeulen suggested below, the UTF-16 array can also be created with map:

let utf16 = stride(from: 24, through: 0, by: -8).map {
    UInt16((value >> $0) & 0xFF)
}

or

let utf16 = [24, 16, 8, 0].map { UInt16((value >> $0) & 0xFF) }

Unless the function is performance critical for your application, pick what you feel most familiar with (otherwise measure and compare).

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I usually use `UInt16(UInt8.max)` instead of the somewhat magical `0xFF`, though I guess it's a matter of experience. I would also recommend using a `map` to create the `chars` array, it would have saves you some time in your last edit :) – Tim Vermeulen Mar 03 '17 at 15:24
  • @TimVermeulen: Thanks for the feedback. I wrote it this way because it is easy to understand, and because I *assume* that it allows the compiler to optimize code better (no methods with callbacks called). – Martin R Mar 03 '17 at 15:26
  • I _think_ the compiler will be able to optimise away the `map`, but you'd have to look at the generated assembly. I'm less confident about the `stride`, but you could always replace it by `[24, 16, 8, 0]` if that turns out to be an issue. – Tim Vermeulen Mar 03 '17 at 15:32
1

I don't test this code but try this:

func interpretAsString(possibleMsg: Int32) -> String {
    var result = String()
    result.append(Character(UnicodeScalar(UInt32(possibleMsg>>24))!))
    result.append(Character(UnicodeScalar(UInt32((possibleMsg>>16) & UInt32(0xFF)))!))
    result.append(Character(UnicodeScalar(UInt32((possibleMsg>>8) & UInt32(0xFF)))!))
    result.append(Character(UnicodeScalar(UInt32((possibleMsg) & UInt32(0xFF)))!))
    return result
}
Sergey
  • 1,589
  • 9
  • 13
0

This may be an old question, but since it was asking in the context of Core Audio, I just wanted to share a variant I was playing with.

For Core Audio, where some (but not all?) OSStatus/Int32 values are defined using four characters, some code from Apple's old Core Audio Utility Classes can provide inspiration (very similar to the linked question)

From CAXException.h:

class CAX4CCStringNoQuote {
public:
    CAX4CCStringNoQuote(OSStatus error) {
        // see if it appears to be a 4-char-code
        UInt32 beErr = CFSwapInt32HostToBig(error);
        char *str = mStr;
        memcpy(str, &beErr, 4);
        if (isprint(str[0]) && isprint(str[1]) && isprint(str[2]) && isprint(str[3])) {
            str[4] = '\0';
        } else if (error > -200000 && error < 200000)
            // no, format it as an integer
            snprintf(str, sizeof(mStr), "%d", (int)error);
        else
            snprintf(str, sizeof(mStr), "0x%x", (int)error);
    }
    const char *get() const { return mStr; }
    operator const char *() const { return mStr; }
private:
    char mStr[16];
};

In Swift 5, one rough translation (without the hex representation for large values) might be:

    private func osStatusToString(_ value: OSStatus) -> String {
        let data = withUnsafeBytes(of: value.bigEndian, { Data($0) })

        // If all bytes are printable characters, we treat it like characters of a string
        if data.allSatisfy({ 0x20 <= $0 && $0 <= 0x7e }) {
            return String(data: data, encoding: .ascii)!
        } else {
            return String(value)
        }
    }

Note that the Data initializer is making a copy of the bytes, though it may be possible to avoid that if desired.

Of course, with Core Audio we encounter four character codes with both Int32 and UInt32 types. I haven't done generics with Swift before, but one way to handle them in a single function could be:

    private func stringifyErrorCode<T: FixedWidthInteger>(_ value: T) -> String {
        let data = withUnsafeBytes(of: value.bigEndian, { Data($0) })

        // If all bytes are printable characters, we treat it like characters of a string
        if data.allSatisfy({ 0x20 <= $0 && $0 <= 0x7e }) {
            return String(data: data, encoding: .ascii)!
        } else {
            return String(value, radix: 10)
        }
    }

This may not be suitable for general purpose handling of four character codes (I've seen other answers that support characters in the MacOS Roman encoding versus ASCII in the example above. There's likely some history there I'm not aware of), but may be reasonable for Core Audio status/selector codes.

user1325158
  • 194
  • 3
  • 9