0

I would assume this to be easy but it's not working. I have an array I want to remove an objet from. Object is of a type protocol.

var myArray = Array<MyProtocol>()
myArray.remove(at: protocolToRemove) 

From another SO question I saw a proposal for Array extension but it's not working when I put the extension in another file.

extension Array where Element: Equatable {

    // Remove first collection element that is equal to the given `object`:
    mutating func remove(object: Element) {
        if let index = index(of: object) {
            remove(at: index)
        }
     }
}

The protocol does not implement Equatable

Edit - Adding more information

MyProtocol Looks like

protocol MyProtocol {
}

Also tried this, but MyProtocol is not AnyObject, even with the class only protocol.

extension Array where Element: AnyObject {
    mutating func remove(_ element: Element) {
        guard let index = index(where: { $0 === element }) else { return }
        remove(at: index)
    }

    func test() {

    }
} 
LEO
  • 2,572
  • 1
  • 26
  • 31

2 Answers2

0

remove(at:) takes an Int (the index of Array). I'm assuming that protocolToRemove is not an Int.

The protocol does not implement Equatable

So how is remove to know which element to remove? If it doesn't support ==, then the only other option is ===, in which case the protocol must be marked as being : class. In that case you can remove the first matching element this way:

protocol MyProtocol: class {
    ....
}

if index = myArray.index(where: { $0 === protocolToRemove }) {
    myArray.remove(at: index)
}
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • interesting about the == vs ===. Putting your suggestion into an extension still does not work. However putting the extension in the class that calls 'remove' seems to work. So created a new problem. – LEO Dec 20 '17 at 01:08
  • I don't understand your point about "in an extension" here. If you mean you want to write this as a general method that works on every possible class-restricted protocol, that probably can't be written today in Swift. If you mean `extension Array where Element == MyProtocol`, that works fine (though it's probably not worth the trouble IMO). – Rob Napier Dec 20 '17 at 02:37
  • I think Array<> should be able to handle the removal of a class-only protocol because of polymorphism – LEO Dec 20 '17 at 22:39
  • Why do you believe that given Swift's current implementation of protocols? If by "should" you mean "Swift some day should be able to do this," I definitely agree. But if you mean "Swift can do this today," I don't know of any evidence for that. There are tons of things that don't work quite as expected with protocols. – Rob Napier Dec 21 '17 at 00:29
0

Rob Napier and Leo Dabus got me down the right path, but Equatable was not the way to go.

Question

How to remove a protocol from an Array<>?

Answer

Create a class-only protocol:

protocol MyProtocol: class {
    //Stuff
}

Add Extension to Array like so:

extension Array {
    mutating func removeObject(element: Element) {
        guard let index = index(where: { $0 as AnyObject === element as AnyObject}) else { return }
        remove(at: index)
    }
}

Usage:

var myArray = Array<MyProtocol>()
var myProtocolImpl = MyProtocolImpl()

myArray.append(myProtocolImpl)
myArray.removeObject(element: myProtocolImpl)

Why

  • Protocol is not type AnyObject.
  • Using Array< /Protocol> never picked up the AnyObject extension.
  • Equatable decoration on a Protocol never picked up the extension.
  • In order to remove an objected based on pointer address being equal, you must compare memory locations and best way to do that is with AnyObject for class-only protocols.
LEO
  • 2,572
  • 1
  • 26
  • 31
  • This solution has surprising edge cases. Try removing `"Bob"` from `["Alice", "Bob"]` (fails). On the other hand, try removing `1` from `[1,2,3]` (succeeds). Understanding why one of these works and the other doesn't is quite subtle and unreliable (and relies on what processor you're running on). Now try removing `Int.max` from `[1,2,3,.max]` (fails). This is not a good general solution. It accepts non-class values, but behaves in unpredictable ways. – Rob Napier Dec 21 '17 at 00:28
  • Yes. I agree with the weaknesses you posted. As long as the reference is the same it removes the object though and fixes the weakness of Protocol. If there was another way to use interfaces in Arrays I'm all down for it. – LEO Dec 21 '17 at 02:22