Search code examples
arraysswiftgenericsprotocolstype-constraints

Require an Array to be a minimum size in Swift-protocol


I have an object that will output pixels line by line (just like old televisions did). This object simply writes bytes into a twodimensional array. So there is a number of horizontal lines with each having a number of pixels. These numbers are fixed: there is x number of horizontal lines, and each lines y number of pixels. A pixel is a struct of red, green, blue.

I would like clients of this class to plug in their own object to which these values can be written, as I would like this code two work well on Apple-platforms (where CALayer is present), but also on other platforms (e.g. Linux, where the rendering needs to be done without CALayer). So I was thinking of making protocols like this:

struct Pixel
{
    var red: UInt8 = 0
    var green: UInt8 = 0
    var blue: UInt8 = 0
}
protocol PixelLine
{
    var pixels: [Pixel] { get }
}
protocol OutputReceivable
{
    var pixelLines: [PixelLine] { get }
}

These protocols woul be used at some point like

let pixelLineIndex = ... // max 719
let pixelIndex = ... // max 1279

// outputReceivable is an object that conforms to the OutputReceivable protocol
outputReceivale.pixelLines[pixelLineIndex][pixelIndex].red = 12
outputReceivale.pixelLines[pixelLineIndex][pixelIndex].green = 128
outputReceivale.pixelLines[pixelLineIndex][pixelIndex].blue = 66

Two questions arise:

  • how to require the protocol PixelLine to have a minimum of 1280 Pixel units in the array and and the protocol OutputReceivable a minimum of 720 PixelLine elements in the array ?

  • as I learned from a video, using generics can help the compiler generate optimal code. Is there a way for me to use generics to generate more performant code then using plain protocols as a type?


Solution

  • There are no dependent types in Swift. You cannot directly require Arrays to be of a minimum size. What you can do is create new types that can only be constructed with particular data. So in this case, the better model would be to make PixelLine a struct rather than a protocol. Then you can have an init? that ensures that it is legal before using it.

    A simple struct wrapper around an Array is zero-cost in memory and extremely low cost in dispatch. If you're dealing with a high performance system, a struct wrapping an Array is an excellent starting point.

    struct PixelLine {
        let pixels: [Pixel]
        init?(pixels: [Pixel]) {
            guard pixels.count >= 1280 else { return nil }
            self.pixels = pixels
        }
    }
    

    You can either expose pixels directly like this does, or you can make PixelLine a Collection (or even just a Sequence) that forwards its required methods to pixels.