Skip to content

Instantly share code, notes, and snippets.

@AliSoftware
Last active March 27, 2024 11:57
Show Gist options
  • Save AliSoftware/9e4946c8b6038572d678 to your computer and use it in GitHub Desktop.
Save AliSoftware/9e4946c8b6038572d678 to your computer and use it in GitHub Desktop.
Swift, Struct & Inheritance: How to balance the will of using Struct & Value Types and the need for Inheritance?
// #!Swift-1.1
import Foundation
// MARK: - (1) classes
// Solution 1:
// - Use classes instead of struct
// Issue: Violate the concept of moving model to the value layer
// http://realm.io/news/andy-matuschak-controlling-complexity/
typealias JSONDict = [NSObject:AnyObject]
class Vehicle1 {
let model: String
let color: String
init(jsonDict: JSONDict) {
model = jsonDict["model"] as String
color = jsonDict["color"] as String
}
}
class Car1 : Vehicle1 {
let horsepower: Double
let license_plate: String
override init(jsonDict: JSONDict) {
super.init(jsonDict: jsonDict)
horsepower = jsonDict["horsepower"] as Double
license_plate = jsonDict["license_plate"] as String
}
}
class Bicycle1 : Vehicle1 {
let chainrings: Int
let sprockets: Int
override init(jsonDict: JSONDict) {
super.init(jsonDict: jsonDict)
chainrings = jsonDict["chainrings"] as Int
sprockets = jsonDict["sprockets"] as Int
}
}
// MARK: - (2) struct + composition
// Solution 2:
// - keep value types
// - use composition.
// Issue: We violate the encapsulation principle, exposing the internal composition to the outside world
struct Vehicle2 {
let model: String
let color: String
init(jsonDict: JSONDict) {
model = jsonDict["model"] as String
color = jsonDict["color"] as String
}
}
struct Car2 {
let vehicle: Vehicle2
let horsepower: Double
let license_plate: String
init(jsonDict: JSONDict) {
vehicle = Vehicle2(jsonDict: jsonDict)
horsepower = jsonDict["horsepower"] as Double
license_plate = jsonDict["license_plate"] as String
}
}
struct Bicycle2 {
let vehicle: Vehicle2
let chainrings: Int
let sprockets: Int
init(jsonDict: JSONDict) {
vehicle = Vehicle2(jsonDict: jsonDict)
chainrings = jsonDict["chainrings"] as Int
sprockets = jsonDict["sprockets"] as Int
}
}
// MARK: - (3) struct, protocol + composition for parsing
// Solution 3:
// - keep value types, use a protocol
// - use intermediate struct only for parsing to keep encapsulation
// Issue: None… except code verbosity
protocol Vehicle3 {
var model: String { get }
var color: String { get }
}
private struct VehicleFields3 : Vehicle3 {
let model: String
let color: String
init(jsonDict: JSONDict) {
model = jsonDict["model"] as String
color = jsonDict["color"] as String
}
}
struct Car3 : Vehicle3 {
let model: String
let color: String
let horsepower: Double
let license_plate: String
init(jsonDict: JSONDict) {
let vehicle = VehicleFields3(jsonDict: jsonDict)
model = vehicle.model
color = vehicle.color
horsepower = jsonDict["horsepower"] as Double
license_plate = jsonDict["license_plate"] as String
}
}
struct Bicycle3 : Vehicle3 {
let model: String
let color: String
let chainrings: Int
let sprockets: Int
init(jsonDict: JSONDict) {
let vehicle = VehicleFields3(jsonDict: jsonDict)
model = vehicle.model
color = vehicle.color
chainrings = jsonDict["chainrings"] as Int
sprockets = jsonDict["sprockets"] as Int
}
}
// MARK: - (4) struct, protocols + global function for parsing
// Solution 4: [Does not compile]
// - keep value types, use a protocol
// - use a global function to fill the objects's fields conforming to the protocol
// Issue: does not work (it seems we can't pass 'self' as inout in the init() method)
// exposes the setter in the protocol and the structs anyway (so bad access protection)
protocol Vehicle4 {
var model: String { get set }
var color: String { get set }
}
private func parseVehicle4Fields(inout obj: Vehicle4, jsonDict: JSONDict) {
obj.model = jsonDict["model"] as String
obj.color = jsonDict["color"] as String
}
struct Car4 : Vehicle4 {
var model: String
var color: String
let horsepower: Double
let license_plate: String
init(jsonDict: JSONDict) {
parseVehicle4Fields(&self, jsonDict) // Error: Car4 is not identical to Vehicle4
horsepower = jsonDict["horsepower"] as Double
license_plate = jsonDict["license_plate"] as String
}
}
struct Bicycle4 : Vehicle4 {
var model: String
var color: String
let chainrings: Int
let sprockets: Int
init(jsonDict: JSONDict) {
parseVehicle4Fields(&self, jsonDict) // Error: Bicycle4 is not identical to Vehicle4
chainrings = jsonDict["chainrings"] as Int
sprockets = jsonDict["sprockets"] as Int
}
}
@mlvea
Copy link

mlvea commented Sep 16, 2015

Thanks for sharing this. Would be great if you add some description to the gist. As you sharing alternative to achieve inheritance flavors within structs I noticed some people on stackoverflow getting confused that this gist claim struct in capable of pure inheritance.

@LukeDefeo
Copy link

Thanks i found this really interesting, 4 is a nice idea conceptually, its a shame it doesnt work.

@ryan-blunden
Copy link

I think it's also essential for models to be Equatable, Hashable (for inclusion in sets) and CustomStringConvertible (for debugging). Would love to see another example that implements those.

@AliSoftware
Copy link
Author

Would be great if you add some description to the gist

@MadNik:Done, thx ;) Didn't realize this gist had such a success, wrote it a while back (might need to be updated for Swift 2 or with other new alternatives the language might maybe offer?)

@AliSoftware
Copy link
Author

BTW, people finding that gist from searching about structs & inheritance in Swift might also be interested in my (way more recent) article dedicated to explain how you could use the Mixins pattern (allowed by Swift 2's default protocol implementations) instead of inheritance 😉

@illusionofchaos
Copy link

@AliSoftware Here is how you can make the fourth solution work. The only downside is that you need to initialize properties before you call parseVehicle4Fields(jsonDict)

protocol Vehicle4 {
    var model: String { get set }
    var color: String { get set }
}

extension Vehicle4 {
    private mutating func parseVehicle4Fields(jsonDict: JSONDict) {
        model = jsonDict["model"] as! String
        color = jsonDict["color"] as! String
    }
}

struct Car4 : Vehicle4 {
    var model = ""
    var color = ""
    let horsepower: Double
    let license_plate: String
    init(jsonDict: JSONDict) {
        horsepower = jsonDict["horsepower"] as! Double
        license_plate = jsonDict["license_plate"] as! String
        parseVehicle4Fields(jsonDict)
    }
}

struct Bicycle4 : Vehicle4 {
    var model = ""
    var color = ""
    let chainrings: Int
    let sprockets: Int
    init(jsonDict: JSONDict) {
        chainrings = jsonDict["chainrings"] as! Int
        sprockets = jsonDict["sprockets"] as! Int
        parseVehicle4Fields(jsonDict)
    }
}

@dunarri
Copy link

dunarri commented Nov 20, 2016

@illusionofchaos I started out with an implementation like that, but didn't like how you have to initialize all the properties with dummy values first. It's fine for scalar values like String, but once your struct has struct / class properties with their own init methods it becomes a pain. It turns out that although you can't call instance methods before all properties are initialized, you CAN call static methods. So my implementation is like this:

protocol Vehicle4 {
    var model: String { get set }
    var color: String { get set }
}

extension Vehicle4 {
    static func parseVehicle4Fields(jsonDict: JSONDict) -> (String, String) {
        let model = jsonDict["model"] as! String
        let color = jsonDict["color"] as! String
        return (model, color)
    }
}

struct Car4 : Vehicle4 {
    var model
    var color
    let horsepower: Double
    let license_plate: String
    init(jsonDict: JSONDict) {
        (model, color) = Car4.parseVehicle4Fields(jsonDict)
        horsepower = jsonDict["horsepower"] as! Double
        license_plate = jsonDict["license_plate"] as! String
    }
}

struct Bicycle4 : Vehicle4 {
    var model
    var color
    let chainrings: Int
    let sprockets: Int
    init(jsonDict: JSONDict) {
        (model, color) = Bicycle4.parseVehicle4Fields(jsonDict)
        chainrings = jsonDict["chainrings"] as! Int
        sprockets = jsonDict["sprockets"] as! Int
    }
}

@shoaibahmedqureshi
Copy link

Can a struct in swift inherit a class ?

@Sethmr
Copy link

Sethmr commented Apr 29, 2020

I like this article. It states the situation well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment