Search code examples
iosswiftprotocolscomparable

Extend @objc protocol with Comparable in Swift


I am trying to extend my protocol Option with Comparable to use simple .sort() method.

Below short example only with Equatable to show errors.

@objc protocol Option: Equatable {
    var title: String { get }
    var enabled: Bool { get }
    var position: Int { get }
}

func ==(lhs: Option, rhs: Option) -> Bool {
    return lhs.position == rhs.position
}

The Option protocol must be marked as @objc or inherit from NSObjectProtocol because it will be used with UIKit.

Errors:

  1. @objc protocol 'Option' cannot refine non-@objc protocol 'Equatable'

  2. Protocol 'Option' can only be used as a generic constraint because it has Self or associated type requirements

Do you have any suggestion how to solve this problem?


Solution

  • Equatable lives in the Swift world only, thus you cannot extend it to a protocol that will be used by Objective-C. Trying to do this results in error #1

    Protocols that have a Self requirement (i.e. at least one method from the protocol declaration contains Self) cannot be used as arguments to functions, or to variable declarations, only as arguments to a generic clause, e.g. func doSomething<T: Option>(argument: T).

    Removing Equatable from the Option protocol declaration, and declaring == as generic on Option will solve the compile errors. As for sorting, you can also overload the < operator, and sort via that operator (without needing to implement Comparable):

    @objc protocol Option {
        var title: String { get }
        var enabled: Bool { get }
        var position: Int { get }
    }
    
    func ==<T: Option>(lhs: T, rhs: T) -> Bool {
        return lhs.position == rhs.position
    }
    
    func <<T: Option>(lhs: T, rhs: T) -> Bool {
        return lhs.position < rhs.position
    }
    

    This allows you to pass objects that conform to the protocol to UIKit, and to also compare them within your swift code.

    class A: NSObject, Option { .. }
    class B: NSObject, Option { ... }
    
    let a = A()
    let b = B()
    a == b  // compiles, and returns true if a and b have the same position
    let c: [Option] = [a, b]
    c.sort(<) // returns a sorted array by the `position` field
    

    One important note regarding the sorting code above: if you don't specify the type for c, then the compiler infers its type as [NSObject], and the sort call will not compile due to ambiguity of the < operator. You need to explicitly declare c as [Option] to take advantage of the overloaded operator.