0
votes

I am currently writing a reusable UI component in Swift that's supposed to be consumed from both Obj-C/ Swift worlds (it's a mixed project). I defined a @objc protocol without any associated types (since those are not allowed for @objc protocols). In one of the methods in the component, I need to store the protocol as a type and need to find the index of a particular entry, somewhat similar to the following-

func select<T: Itemable>(_ item: T) {
    guard let itemIndex = items.index(of: item) else {
        return
    }
  //more stuf
}

where items is an array of Itemable (protocol) type.

However, I get the error saying I can not use it as a type conforming to Equatable since equatable has static requirements. Itemable is defined as following-

@objc protocol Itemable { //methods and properties }

Also, not sure how to make it conform to equatable. Apparently, the following helps but not sure why-

func ==<T: <Itemable>>(lhs: T, rhs: T) -> Bool {
    return lhs.aProperty == rhs.aProperty
}

Seems to me like it might require type erasing, but not sure how to go about doing that.

Here's an abridged version of the protocol, showing all different types of methods and properties present- it does not really have anything static or associated type.

 @objc protocol Itemable {
    typealias voidBlock = () -> Void
    var color: UIColor { get }
    var screenParameters: [String : String] { get }
    var screenView: String { get }
    var iconImage: UIImage? { get }
    @objc var accessibilityLabel: String? { get }
}

extension Array where Element: Itemable {
    func index(of element: Element) -> Int? {
        index(where: { $0.screenView == element.screenView })
    }
} 
1
Can you show more of the code around guard statement in question. Can you show the method/class it is in?Sweeper
Done! Please take a look again.Subzero
I'm guessing items is [Itemable]? In that case, I think one way (not sure if it's the only way) is to do as NSArray and use index(of:) in NSArray.Sweeper

1 Answers

0
votes

You cannot make an @objc type conform to Equatable. Equatable has a Self requirement. Objective-C protocols cannot express a Self requirement.

Your == function is usable, but it doesn't cause the type to conform to Equatable. It just means you can evaluate item == item. You cannot, however, call items.contain(item) since Itemable doesn't conform to Equatable. What you can do is call items.contains{$0 == item} since that just requires the == function, not Equatable. And of course you could implement a custom .contains method for [Itemable] if you wanted one. But it still wouldn't be Equatable.

For your example, I believe you want to get rid of == entirely, and use this:

guard let itemIndex = items.index(where: { $0.aProperty == item.aProperty }) else {

If you do this a lot, you can of course add an extension on [Itemable] (untested):

extension Array where Element: Itemable {
    func index(of element: Element) -> Int? {
        firstIndex(where: { $0.aProperty == element.aProperty })
    }
}

Then your original code would be fine.

Somewhat unrelated to your question: It's possible this is simplified code, but be very careful of this kind of implementation of ==. First, == should always test all visible properties. If, for example, aProperty is just the ID, then that is a dangerous way to implement ==. When two things are equal (in both ObjC and Swift), they are expected to be interchangeable in all contexts. If you ever care which one you have, they're not really "equal."

Also, when two things are equal, they should have the same hash (and if they're Equatable, they must have the same hash).

See the docs on Conforming to the Equatable Protocol for the rules. While == doesn't technically imply Equatable, it is confusing to use it if you don't mean Equatable.