Complex Swift protocol example
/*
A protocol in Swift is similar to interfaces in object-oriented languages where the protocol
acts as a contract that defines the methods, properties, and other requirements needed by our
types to perform their task.
Protocol composition allows us to break our requirements into many smaller components rather
than inheriting all requirements from a single superclass or class hierarchy. This allows our
type families to grow in width rather that height, which means we avoid creating bloated types
that contain requirements that are not needed.
*/
protocol Insurable {
var insured: Bool { get set }
var insuranceProvider: InsuranceProvider? { get set }
func getInsurancePremium() -> Double
}
protocol Paintable {
var painted: Bool { get set }
var color: Color? { get set }
func getInsurancePriceModifier() -> Double
}
// Protocols can be inherited
protocol Vehicle: Paintable {
var make: String { get }
var model: String { get }
var type: VehicleType { get }
//Initializers can be specified if necessary
//init(make:String, model:String, vehicleType type: VehicleType, color:Color?)
func getMake() -> String
func getMakeAndModel() -> String
}
struct Car: Vehicle, Insurable {
let make: String
let model: String
let type: VehicleType
var color: Color? {
didSet {
painted = color != nil
}
}
var insuranceProvider: InsuranceProvider? {
didSet {
insured = insuranceProvider != nil
}
}
var insured: Bool
var painted: Bool
init(make: String, model: String, vehicleType type: VehicleType,
color:Color? = nil, insuranceProvider ins: InsuranceProvider? = nil) {
self.make = make
self.model = model
self.type = type
self.color = color
self.insuranceProvider = ins
insured = insuranceProvider != nil
painted = color != nil
}
func getInsurancePremium() -> Double {
return 100 + getInsurancePriceModifier()
}
func getInsurancePriceModifier() -> Double {
guard let color = color else {
return 0
}
switch color {
case .Black:
return 40
case .Blue:
return 25
case .Red:
return 10.50
case .White:
return -25
}
}
// protocol extension methods can be overriden if necessary
func getMake() -> String {
return "\(make)"
}
}
// Protocols can be extended to provide default implementations
extension Vehicle {
func getMake() -> String {
return "Function getMake() not implemented"
}
func getMakeAndModel() -> String {
return "Make: \(make), Model: \(model)"
}
}
/* Supporting Code */
enum Color {
case Black, Blue, Red, White
}
enum InsuranceProvider {
case Geico, Progressive, StateFarm
}
enum VehicleType {
case Car, Motorcycle, Trike, Truck
var wheels: Int {
switch self {
case .Car:
return 4
case .Motorcycle:
return 2
case .Trike:
return 3
case .Truck:
return 4
}
}
}
// Protocols can be used as types
func getColor(vehicle: Vehicle) -> String {
guard let color = vehicle.color else {
return "Vehicle has no color"
}
return "\(color)"
}
let fooCar = Car(make: "Foo", model: "Bar", vehicleType: .Car)
assert(getColor(fooCar) == "Vehicle has no color")
var vehicleArray = [Any]()
vehicleArray.append(fooCar)
assert((vehicleArray[0] as? Vehicle) != nil)
// Polymorphism and protocols
struct OffroadTruck: Vehicle {
var make: String
var model: String
var type: VehicleType
var painted: Bool
var color: Color?
init(make: String, model: String, vehicleType type: VehicleType,
color:Color? = nil) {
self.make = make
self.model = model
self.type = type
self.color = color
painted = color != nil
}
func getInsurancePriceModifier() -> Double {
return 0.00
}
}
let truck = OffroadTruck(make: "Tonka", model: "Dump Truck", vehicleType: .Truck, color: .White)
var vehicles = [Vehicle]()
vehicles.append(truck)
vehicles.append(fooCar)
// Conformance to a protocol can be checked with 'is' and it can be "cast" with 'as'
var anyInstance: Any = truck
assert(anyInstance is Vehicle)
if let truck = anyInstance as? Vehicle {
print("cast successful")
}
var anyInstanceArray: [Any] = [Any]()
anyInstanceArray.append("Random String")
anyInstanceArray.append(fooCar)
//Sorting generic type with 'is'
for instance in anyInstanceArray where instance is Vehicle {
print((instance as! Vehicle).model)
}
var mixedVehicles: [Vehicle] = [fooCar, truck]
//Sorting by protocol with 'is'
for vehicle in mixedVehicles where vehicle is Insurable {
print("\(vehicle.model) is insurable")
}
//'is' in switch statements
switch(anyInstanceArray[1]) {
case is Vehicle:
print("Vehicle detected")
default:
print("Not a vehicle")
}
/* Test Cases */
//if struct is to be mutable use var...
var newCar = Car(make: "Ford", model: "Mustang", vehicleType: .Car)
//Assert init()
assert(newCar.insuranceProvider == nil)
assert(newCar.color == nil)
assert(!newCar.insured)
assert(!newCar.painted)
//Assert for extension default implementation
assert(newCar.getMakeAndModel() == "Make: Ford, Model: Mustang")
//Assert for overridden extension implementation
assert(newCar.getMake() == "Ford")
//Assert for 'didSet' functionality on insured property
assert(!newCar.insured)
newCar.insuranceProvider = InsuranceProvider.Geico
assert(newCar.insured) //DidSet sets insured on insurance provider property update
//Vehicle type enum property check
assert(newCar.type.wheels == 4)
//Protocol as type
//getColor(_: Vehicle)
assert(getColor(newCar) == "Vehicle has no color")
newCar.color = Color.Black
assert(getColor(newCar) == "Black")