ahcode0919
7/12/2016 - 12:39 AM

Complex Swift protocol example

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")