Jacob
Jacob

Reputation: 1062

Making `Identifiable` protocol backwards compatible

Problem

Apple made Identifiable only available in iOS 13+. It's a super useful protocol, however I ended up using my own "Identifiable" protocol named CompatibilityIdentifiable.

public protocol CompatibilityIdentifiable {
    associatedtype Id: Equatable
    var id: Id { get }
}

Question:

Is there any way to make my custom CompatibilityIdentifiable also conform to Identifiable on iOS 13+?

Background:

In a regular project this has no use. But in an SPM package it means a lot because I can implement the same behaviour for CompatibilityIdentifiable as for Identifiable without duplicating my code.

Here is an example:

extension TableViewHelper where Section: CompatibilityIdentifiable {
    public func replace(sections: [Section], with animation: UITableView.RowAnimation = .automatic) {
        replaceSections(with: animation) { oldSection in
            return sections.first(where: { $0.id == oldSection.id })
        }
    }
}

If I can simply make CompatibilityIdentifiable conform to Identifiable I can use Identifiable for projects that are iOS 13+

I honestly don't think this is possible unless I simply copy and paste the code like this:

@available(iOS 13, *)
extension TableViewHelper where Section: Identifiable {
    public func replace(sections: [Section], with animation: UITableView.RowAnimation = .automatic) {
        replaceSections(with: animation) { oldSection in
            return sections.first(where: { $0.id == oldSection.id })
        }
    }
}

I won't be doing this as we're talking about a lot of functions. So, if there is no way, I will continue using CompatibilityIdentifiable for my future projects and my SPMs

Upvotes: 1

Views: 209

Answers (1)

Cristik
Cristik

Reputation: 32783

Unfortunately, there's no way to make your protocol inherit from Identifiable, but only from iOS 13 and above.

The reason is you cannot have two declarations of the same protocol, one for iOS 12-, and one for iOS 13+.

With Objective-C protocols, this might have been possible via some #ifdef dance, but since Identifiable is a Swift-only protocol, and since you're targeting a Swift package, you're out of luck.

However, if you refactor your code a little bit, you might be able to support both Identifiable, and CompatibilityIdentifiable with less duplication:

extension Array where Element: CompatibilityIdentifiable {
    func firstWithSameId(as other: Element) {
       first(where: { $0.id == other.id })
    }
}

extension Array where Element: Identifiable {
    func firstWithSameId(as other: Element) {
       first(where: { $0.id == other.id })
    }
}

extension TableViewHelper {
    // mind the `internal` access modifier here (explicitly written)
    internal func replaceSection(with animation: UITableView.RowAnimation, replacement: (Section) -> Section?) {
        replaceSections(with: animation) { replacement($0) })
        // or replaceSections(with: animation, secondParam: replacement)
    }

    public func replace(sections: [Section], with animation: UITableView.RowAnimation = .automatic) where Section: CompatibilityIdentifiable {
        replaceSections(with: animation, replacement: sections.firstWithSameId)
    }

    @available(iOS 13, *)
    public func replace(sections: [Section], with animation: UITableView.RowAnimation = .automatic) where Section: Identifiable {
        replaceSections(with: animation, replacement: sections.firstWithSameId)
    }
}

Even if the two public functions looks the same, they have only one line of code, which is easier to grasp in the economy of code duplication.

Also there's more code than by simply copy+pasting, however the two Array extensions might be usable in other context too.

Upvotes: 1

Related Questions