How to protected your APIs

There has been much talk on the dev forums about the lack of protected in the Swift language. My personal opinion? I do not like the protected construct nor do I think it fits with Swift's other access modifiers as it would then be the only class-scoped access modifier.

Anyway, I have been playing around with how we can mimick one of the key desires: limiting the public API scope.

WARNING: This solution is very much an experiment to see if what it might be like. Do not take this as something you should do in your code. USE AT YOUR OWN RISK.

: .warning

Ok… the basic premise of the idea is this: subclasses should be able to have full access to a set of data within a class. However, we to make make it "hard" for general consumers of the API to use it.

public class Vehicle {
    // This is how we "hide" the API from the general user.
    public class Protected {
        public var numberOfAxles: Int = 0
        public init() {}
    }
    public private(set) var protected: Protected = Protected()

    public init() {}

    // All us to copy the protected state down the inheritance chain
    public init(protected: Protected) {
        self.protected = protected
    }

    // Our first addition to the inheritance model
    public var numberOfWheels: Int {
        assert(false, "This should really be abstract")
        return 0
    }
}

Alright, this is pretty straight-forward. I have created an inner class that presents the protected data for the vehicle and exposed all of that through a single, public member: protected. So we have "polluted" our public API with a single item instead of multiple and make it clear (by convention) that this is an unsafe area to be in.

Let's add some cars now:

public class Car : Vehicle {
    public class ProtectedCar : Vehicle.Protected {
        public var wheelSize: Int = 14
        public init() {
            super.init()
            self.numberOfAxles = 2
        }
    }

    public init() {
        super.init(protected: ProtectedCar())
    }

    override public var numberOfWheels: Int {
        return 4
    }

    public func wheelSize() -> Int {
        return (self.protected as ProtectedCar).wheelSize
    }
}

public class SportsCar : Car {
    public init() {
        super.init()
        (self.protected as ProtectedCar).wheelSize = 18
    }
}

The Car class has some additional protected data, so I created another inner-class that inherits from Vehicle.Protected. I also created a true public API that retrieves some of that inner data: wheelSize().

And here are the test cases to see it in action:

func testCar() {
    let car = Car()
    XCTAssertEqual(car.protected.numberOfAxles, 2)
    XCTAssertEqual(car.numberOfWheels, 4)
    XCTAssertEqual(car.wheelSize(), 14)
}

func testSportsCar() {
    let car = SportsCar()
    XCTAssertEqual(car.protected.numberOfAxles, 2)
    XCTAssertEqual(car.numberOfWheels, 4)
    XCTAssertEqual(car.wheelSize(), 18)
}

func testSportsCarAsCar() {
    let car: Car = SportsCar()
    XCTAssertEqual(car.protected.numberOfAxles, 2)
    XCTAssertEqual(car.numberOfWheels, 4)
    XCTAssertEqual(car.wheelSize(), 18)
}

func testSportsCarAsCarToFunction() {
    func inner(car: Car) {
        XCTAssertEqual(car.protected.numberOfAxles, 2)
        XCTAssertEqual(car.numberOfWheels, 4)
        XCTAssertEqual(car.wheelSize(), 18)
    }

    let car: Car = SportsCar()
    inner(car)
}

func testSportsCarAsVehicleToFunction() {
    func inner(vehicle: Vehicle) {
        XCTAssertEqual(vehicle.protected.numberOfAxles, 2)
        XCTAssertEqual(vehicle.numberOfWheels, 4)
    }

    let car = SportsCar()
    inner(car)
}

If you are of the mindset that the protected construct is one that is rare and really is about setting boundaries for your developers to not accidently call your APIs that are really not intended for non-subclass types, then this inner-type methodology might be OK for you.

Will I be using this pattern? Probably not. But, it is an initial experiment that I'm sure others may want to improve upon if the protected construct never comes and there is no other alternatives.

How to protected your APIs