Use different implementations of a class depending on iOS version?
Asked Answered
B

4

6

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

Benzidine answered 7/9, 2017 at 16:40 Comment(9)
It would help if you include some relevant code in your question. Show an example of what you are doing and clearly describe what you would like to do.Wille
I think the real problem is that #available has no converse. For example, you can include stuff if this is iOS 11 but you can't exclude stuff if this is iOS 11.Woothen
Does the two versions of your class have exactly the same members?Baseline
It does not have the same members. One of the versions has about half of the class variables that the other has. Basically I have a server class that uses Reachability to re-make network calls and iOS 11 introduced a "waitsForConnectivity" flag that sounds like it may do a lot of what I did, so I don't need as many variables to keep track of network stuff. I'll try to figure out a slimmed down version of my code that I can post later.Benzidine
@Woothen Sure you can. Just include an else block after your if block.Shrug
This might be helpful #44424837Literally
@CharlesSrstka But can you do that when the available is attached to a method declaration itself? That's the problem I've had.Woothen
@Woothen In that case, you'd be using @available, not #available.Shrug
Anyway, Hans' answer below is more or less what I was going to write, so that's what I'd recommend.Shrug
F
9
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).

Fraser answered 7/9, 2017 at 19:46 Comment(0)
C
5

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.

Clarabelle answered 7/9, 2017 at 20:36 Comment(0)
W
0

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

Woothen answered 7/9, 2017 at 16:53 Comment(0)
H
0

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() { .. }
Hylotheism answered 26/2, 2018 at 12:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.