How to properly use subclasses in Swift 5

swift cannot override with a stored property
swift protocol
multiple inheritance in swift
types of inheritance in swift
call subclass method from superclass swift
property does not override any property from its superclass
swift extension
polymorphism in swift

I'm trying to have a generic Plugin class and then subclasses such as PluginOne and PluginTwo that would extend the class by adding a function run() and output property so each plugin can execute a custom command and save the output.

Something like this:

class Plugin: Decodable {
  var name: String

  init(name: String) {
    self.name = name
  }
}

class PluginOne: Plugin {
  var output: String?

  init(name: String, output: String) {
    self.output = output

    super.init(name: name)
  }

  func run() {
    // do something
    self.output = "Some output"
  }  
}

class PluginTwo: Plugin {
  var output: String?

  init(name: String, output: String) {
    self.output = output

    super.init(name: name)
  }

  func run() {
    // do something
    self.output = "Some other output"
  }  
}

Now I'm getting the list of available plugins from a json:

let json = """
[
  { "type": "PluginOne", "name": "First plugin of type one" },
  { "type": "PluginOne", "name": "Second plugin of type one" },
  { "type": "PluginTwo", "name": "abcd" }
]
"""

And I'm decoding the file into [Plugin]:

let decoder = JSONDecoder()
let jsonData = Data(json.utf8)
let plugins = try decoder.decode([Plugin].self, from: jsonData)

Now the question is how to properly create subclasses PluginOne and PluginTwo from each Plugin in order to run() each of them?

Also I understand I'm doing something wrong and should maybe decode into a subclass right away (how?) and/or use protocols instead of subclasses.

Please advise

Result of executing the first answer:

import Foundation

let json = """
[
  { "type": "PluginOne", "name": "First plugin of type one" },
  { "type": "PluginOne", "name": "Second plugin of type one" },
  { "type": "PluginTwo", "name": "abcd" }
]
"""


class Plugin: Decodable {
  var name: String

  init(name: String) {
    self.name = name
  }
}

class PluginOne: Plugin {
  var output: String?

  init(name: String, output: String) {
    self.output = output

    super.init(name: name)
  }

  required init(from decoder: Decoder) throws {
    fatalError("init(from:) has not been implemented")
  }

  func run() {
    // do something
    self.output = "Some output"
  }
}

class PluginTwo: Plugin {
  var output: String?

  init(name: String, output: String) {
    self.output = output

    super.init(name: name)
  }

  required init(from decoder: Decoder) throws {
      fatalError("init(from:) has not been implemented")
  }

  func run() {
    // do something
    self.output = "Some other output"
  }
}

let decoder = JSONDecoder()
let jsonData = Data(json.utf8)
let plugins = try decoder.decode([Plugin].self, from: jsonData)

for plugin in plugins {
    if let pluginOne = plugin as? PluginOne {
        pluginOne.run()
        print(pluginOne.output ?? "empty one")
    }
    else if let pluginTwo = plugin as? PluginTwo {
        pluginTwo.run()
        print(pluginTwo.output ?? "empty two")
    } else {
        print("error")
    }
}
// Result: error error error

In answer to the question "how to properly use subclasses" is often "don’t". Swift offers a different paradigm: Rather than object oriented programming, we employ protocol oriented programming as outlined in WWDC 2016 video Protocol and Value Oriented Programming in UIKit Apps.

protocol Plugin: Decodable {
    var name: String { get }

    func run()
}

struct PluginOne: Plugin {
    let name: String

    func run() { ... }
}

struct PluginTwo: Plugin {
    let name: String

    func run() { ... }
}

The question is then "how to I parse the JSON", and we would employ the techniques outlined in the "Encode and Decode Manually" section of the Encoding and Decoding Custom Types document:

struct Plugins: Decodable {
    let plugins: [Plugin]

    init(from decoder: Decoder) throws {
        enum AdditionalInfoKeys: String, CodingKey {
            case type
            case name
        }

        var plugins: [Plugin] = []

        var array = try decoder.unkeyedContainer()

        while !array.isAtEnd {
            let container = try array.nestedContainer(keyedBy: AdditionalInfoKeys.self)

            let type = try container.decode(PluginType.self, forKey: .type)
            let name = try container.decode(String.self, forKey: .name)

            switch type {
            case .pluginOne: plugins.append(PluginOne(name: name))
            case .pluginTwo: plugins.append(PluginTwo(name: name))
            }
        }

        self.plugins = plugins
    }
}

With

enum PluginType: String, Decodable {
    case pluginOne = "PluginOne"
    case pluginTwo = "PluginTwo"
}

You can then do things like:

do {
    let plugins = try JSONDecoder().decode(Plugins.self, from: data)
    print(plugins.plugins)
} catch {
    print(error)
}

That gives you your array of objects that conform to the Plugin protocol.

How to properly use subclasses in Swift 5, In answer to the question “how to properly use subclasses” is often “don't”. Swift offers a different paradigm: Rather than object oriented  According to the iBook released by Apple, extensions in Swift can: Add computed properties and computed static properties Define instance methods and type methods Provide new initializers Define subscripts Define and use new nested types Make an existing type conform to a protocol. Excerpt From: Apple Inc. “The Swift Programming Language.

The best method is definitely protocols. However, if you want to do this, you could make use of Swift's nice optional casting functionality:

for plugin in plugins {
    if let pluginOne = plugin as? PluginOne {
        pluginOne.foo = 0// If you need to set some subclass-specific variable
        pluginOne.run()
    }
    else if let pluginTwo = plugin as? PluginTwo {
        pluginTwo.bar = 0
        pluginTwo.run()
    }
}

If you wanted to use protocols instead:

protocol Runnable {//Our new protocol, only containing one method
    func run()
}

class Plugin: Decodable {
    name: String

    init(name: String) {
        self.name = name
    }
}

class PluginOne: Plugin, Runnable { //implements Runnable protocol
    output: String?

    init(name: String) {
        self.output = output

        super.init(name: name)
    }

    func run() {
        // do something
        self.output = "Some output"
    }  
}

class PluginTwo: Plugin, Runnable { //Also implements runnable protocol
    output: String?

    init(name: String) {
        self.output = output

        super.init(name: name)
    }

    func run() {
        // do something
        self.output = "Some other output"
    }  
}

//.....

let plugins: [Runnable] = load("plugins.json")//Now we have an array of Runnables!
for plugin in plugins {
    plugin.run()//We know that plugin will implement the Runnable protocol,
                //so we know it contains the run() method!
}

An Introduction to Swift Subclassing and Extensions, Do you want to try some new features? By joining the In Swift a subclass can only be derived from a single direct parent class. This is a concept Another way to add new functionality to a Swift class is to use an extension. Extensions can be​  Swift 4 does not allow its subclasses to inherit its superclass initializers for their member types by default. Inheritance is applicable to Super class initializers only to some extent which will be discussed in Automatic Initializer Inheritance.

I think you need to separate between the plugins and management of the plugins since the json contains a list of plugins to load, create or run and not the actual plugins. So for this solution I created a separate PluginManager to hold the plugins and also a protocol and enum to use by the manager

protocol Plugin { //Protocol each plugin must conform to
    func run() -> ()
}

enum PluginType: String { // All supported plugins. To simplify the code but not necessary
    case pluginOne = "PluginOne"
    case pluginTwo = "PluginTwo"
}

The manager class itself, it has an add method for adding plugins from json data and as an example a runAll method that runs all plugins

struct PluginManager {
    var plugins: [String: Plugin]

    init() {
        plugins = [:]
    }

    mutating func add(_ type: String, name: String) {
        var plugin: Plugin?
        switch PluginType.init(rawValue: type) {
        case .pluginOne:
            plugin = PluginOne()
        case .pluginTwo:
            plugin = PluginTwo()
        default:
            print("warning unknow plugin type: \(type)")
        }
        if let plugin = plugin {
            plugins[name] = plugin
        }
    }

    func runAll() {
        for (name, plugin) in plugins {
            print("Executing \(name)")
            plugin.run()
        }
    }
}

The json decoding has been simplified to decode into a dictionary and then use that dictionary to add plugins to the manager

var pluginManager = PluginManager()
do {
    let plugins = try JSONDecoder().decode([[String: String]].self, from: json)
    for plugin in plugins {
        if let type = plugin["type"], let name = plugin["name"] {
            pluginManager.add(type, name: name)
        }
    }
    pluginManager.runAll()

} catch {
    print(error)
}

Inheritance, name before the superclass name, separated by a colon: class SomeSubclass: SomeSuperclass { Swift version: 5.1. Subclassing UIApplication allows you to override functionality such as opening URLs or changing your icon, but it’s a non-trivial task in Swift because of the @UIApplicationMain attribute. If you look in your AppDelegate.swift file you’ll see @UIApplicationMain appears directly before class AppDelegate – this is a special attribute that tells the Swift compiler to generate code to launch your application using default settings.

Types of inheritance is supported in Swift, What is a computed or calculated property in a class in Swift? If you have a mixt codebase I would encourage you to subclass NSObject when creating a new class: Swift classes that are subclasses of NSObject: are Objective-C classes themselves. use objc_msgSend () for calls to (most of) their methods. provide Objective-C runtime metadata for (most of) their method implementations.

Properties, Which are the special types in Swift that are used for working with non specific types? Create a new file and choose Cocoa Touch Class. Click Next and name the class “Person”, type “NSObject” for "Subclass of", then click Next and Create to create the file. NSObject is what's called a universal base class for all Cocoa Touch classes. That means all UIKit classes ultimately come from NSObject, including all of UIKit.

Any vs. AnyObject in Swift 3.0 - Luna An, Secondly, when we initialize the Subclass , we use the super.init() method to pass the parameters needed by the superclass constructor since a  Deinitializers are called automatically, just before instance deallocation takes place. You are not allowed to call a deinitializer yourself. Superclass deinitializers are inherited by their subclasses, and the superclass deinitializer is called automatically at the end of a subclass deinitializer implementation.

Comments
  • I could probably split json file into multiple by type and then decode into appropriate plugin subclass. Is this the best approach though?
  • One more question. I'm struggling to make Plugin not only Decodable but also Identifiable. Protocol type 'Plugin' cannot conform to 'Identifiable' because only concrete types can conform to protocols is the error. Any guesses?
  • Thank you. What if I need to set a certain subclass-specific variable before executing run()? Also would you mind sharing a simple example of how could I use protocols in this scenario?
  • Also I don't understand how in your proposed solution it'll ever get into else. Could you clarify?
  • @Maklaus I edited my post to answer your first questions. As to your second question, if plugin cannot be cast to PluginOne, then we will enter the else if statement.
  • If you look at the json file I linked the data structures are the same so optional downcasting will always downcast into PluginOne, am I correct? As of protocols I'm trying to run it in playgrounds and getting 'required' initializer 'init(from:)' must be provided by subclass of 'Plugin'. I've seen this error before and couldn't figure it out. Could you help?
  • I guess my biggest confusion right now is how can I let Swift compiler know which subclass to downcast/decode to since the data structures are the same, only certain values are different.
  • Also a great answer. Thank you.