1
votes

Trying to extend an Array, String and Dictionary in Swift 4.1 using conditional conformance but am running into a dead end when trying to initialize an array with Elements that are conforming to the Decodable/Encodable through a custom protocol called BinaryCodable.

The following excerpt is from https://github.com/mikeash/BinaryCoder but has been adjusted to use Swift's new conditional conformances in order for it to compile.

extension Array: BinaryCodable where Element: BinaryDecodable, Element: BinaryEncodable {
    public func binaryEncode(to encoder: BinaryEncoder) throws {
        try encoder.encode(self.count)
        for element in self {
            try element.encode(to: encoder)
        }
    }

    public init(fromBinary decoder: BinaryDecoder) throws {
        let binaryElement = Element.self

        let count = try decoder.decode(Int.self)
        self.init()
        self.reserveCapacity(count)
        for _ in 0 ..< count {
            let decoded = try binaryElement.init(from: decoder)
            self.append(decoded)
        }
    }
}

extension String: BinaryCodable {
    public func binaryEncode(to encoder: BinaryEncoder) throws {
        try (Array(self.utf8) as! BinaryCodable).binaryEncode(to: encoder)
    }

    public init(fromBinary decoder: BinaryDecoder) throws {
        let utf8: [UInt8] = try Array(fromBinary: decoder)
        if let str = String(bytes: utf8, encoding: .utf8) {
            self = str
        } else {
            throw BinaryDecoder.Error.invalidUTF8(utf8)
        }
    }
}

However, I'm getting:

Cannot convert value of type 'Array<_>' to specified type '[UInt8]'

For this line:

let utf8: [UInt8] = try Array(fromBinary: decoder)

Any help would be appreciated.

2

2 Answers

1
votes

In order for Array<UInt8> to be BinaryCodable, its element type UInt8 must be BinaryCodable, which it isn't. That protocol has default implementations of its required methods, but conformance must still be declared explicitly:

extension UInt8: BinaryCodable {}

Then your extension String compiles, and you can even get rid of the forced cast as! BinaryCodable in the encoding method (and using guard allows to save one line):

extension String: BinaryCodable {
    public func binaryEncode(to encoder: BinaryEncoder) throws {
        try Array(self.utf8).binaryEncode(to: encoder)
    }

    public init(fromBinary decoder: BinaryDecoder) throws {
        let utf8: [UInt8] = try Array(fromBinary: decoder)
        guard let str = String(bytes: utf8, encoding: .utf8) else {
            throw BinaryDecoder.Error.invalidUTF8(utf8)
        }
        self = str
    }
}
0
votes

Change it to this if you expect each element of the array to have a value:

let utf8:Array<UInt8> = try Array(from: decoder);

Change it to this if you expect some elements of the array to have null or empty value:

let utf8:Array<UInt8?> = try Array(from: decoder);