Use different implementations of a class depending on iOS version?

4.8k views Asked by At

iOS 11 recently added a new feature I would like to use but I still need to support older versions of iOS. Is there a way to write the same class twice and have newer versions of iOS use one version of the class and older versions of iOS use the other?

(Note: Originally I used if #available(iOS 11, *) but I had to use it in so many places that I thought it would just be cleaner to have 2 versions of the class if possible. Maybe there's a way of using @availble somehow? I was focused on using @available rather than pre-compiler #IFDEF stuff because it seems like the "available" tags are the preferred way to do it in Swift now?)

4

There are 4 answers

0
vikingosegundo On

High quality code follows several principles. One would be the Single Responsible Principle, that states that a class should have only one responsibility, or — as Uncle Bob says — there should be just one reason for a class to change.
Another principle is the Dependency Inversion Principle: A class should not depend on lower level classes, but on abstractions (protocols) that these lower level classes implements. This also means that all dependences must be passed into the class that uses them.

Applied on your question one solution could be:

  1. A view controller has a datasource property that is defined as a protocol.
  2. Several classes implement this protocol, each for different iOS versions.
  3. A class exists which only preps is to select the right version. This version selection can be done in many ways, I stick with #available

The datasource protocol:

protocol ViewControllerDataSourcing: class {
    var text:String { get }
}

and it's implementations:

class ViewControllerDataSourceIOS10: ViewControllerDataSourcing {
    var text: String {
        return "This is iOS 10"
    }
}

class ViewControllerDataSourceIOS11: ViewControllerDataSourcing {
    var text: String {
        return "This is iOS 11"
    }
}

class ViewControllerDataSourceIOSUnknown: ViewControllerDataSourcing {
    var text: String {
        return "This is iOS Unknown"
    }
}

The class that selects the right version class:

class DataSourceSelector{
    class func dataSource() -> ViewControllerDataSourcing {
        if #available(iOS 11, *) {
            return ViewControllerDataSourceIOS11()
        }
        if #available(iOS 10, *) {
            return ViewControllerDataSourceIOS10()
        }
        return ViewControllerDataSourceIOSUnknown()
    }
}

and finally the view controller

class ViewController: UIViewController {

    var dataSource: ViewControllerDataSourcing?
    
    @IBOutlet weak var label: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
        self.dataSource = DataSourceSelector.dataSource()
        label.text = dataSource?.text
    }
}

This is a very simple example that should highlight the different components in charge.

0
matt On

"Two versions of a class" sounds like a base class and two subclasses. You can instantiate the desired subclass based on the system version at runtime by interrogating e.g. ProcessInfo.

0
hfehrmann On
protocol Wrapper {

}

extension Wrapper {
    static func instantiate(parametersAB: Any*) -> Wrapper{
        if #available(iOS 11.0, *) {
            return A(parametersA)
        } else {
            return B(parametersB)
        }
    }
}

@available(iOS 11.0,*)
class A: Wrapper {
    //Use new feature of iOS 11
    init(parametersA: Any*) {
      ...
    }
}

class B: Wrapper {
    //Fallback
    init(parametersB: Any*) {
      ...
    }
}

Now you get your instance by calling Wrapper.instationate(params) and with this you forget about the check in the rest of the code, and uses the new feature if it possible.

This solution is only possible if you can establish the same interface for both classes (even if it is a dummy version of the method).

0
Menno On

I ran into this same problem. I ended up solving this problem by adding @available above the methods that I only wanted to support in a particular version of iOS:

@available(iOS 11.3, *)
func myMethod() { .. }