-->

Find delegate in a swift Array of delegates

2020-02-29 01:01发布

问题:

I want to check if I already have a delegate in my removeDelegate method before removing. How do I do that?

Here's what I've got so far:

protocol LocationManagerDelegate {
    func locationManagerDidUpdateLocation(
        oldLocation: CLLocationCoordinate2D,
        currentLocation: CLLocationCoordinate2D
    )
}

class LocationManager: NSObject {
    private var _delegates = [LocationManagerDelegate]()

    func removeDelegate(delegate:LocationManagerDelegate) {
        if contains(_delegates, delegate) {
            // Remove delegate
        }
    }
}

However, this gives me the following error on the 'if contains' line:

cannot invoke 'contains' with an argument list of type '(@lvalue Array< LocationManagerDelegate >!, LocationManagerDelegate)'

回答1:

Update for Swift 3:

Assuming that the delegates are actually instances of a class, you could require that in the protocol by "inheriting" from "class":

protocol LocationManagerDelegate: class {
    // ...
}

and then use the index(where:) method, using the "indentitiy operator ===:

class LocationManager: NSObject {
    private var _delegates = [LocationManagerDelegate]()

    func removeDelegate(delegate:LocationManagerDelegate) {
        if let index = _delegates.index(where: { $0 === delegate }) {
            _delegates.remove(at: index)
        }
    }
}

Old answer (Swift 1):

There are two slightly different contains() functions:

func contains<S : SequenceType where S.Generator.Element : Equatable>(seq: S, x: S.Generator.Element) -> Bool

func contains<S : SequenceType, L : BooleanType>(seq: S, predicate: (S.Generator.Element) -> L) -> Bool

You are using the first one, which requires that the sequence elements conform to the Equatable protocol, i.e. they can be compared with ==.

Assuming that the delegates are actually instances of a class, you could require that in the protocol by "inheriting" from "class":

protocol LocationManagerDelegate : class {
    // ...
}

and then use the second, predicate-based version of contains() with the identity operator ===:

func removeDelegate(delegate:LocationManagerDelegate) {
    if contains(_delegates, { $0 === delegate }) {
        // Remove delegate
    }
}

To remove the object from the array you'll have to get its index, so you might use the findIdenticalObject() function from https://stackoverflow.com/a/25543084/1187415:

func findIdenticalObject<T : AnyObject>(array: [T], value: T) -> Int? {
    for (index, elem) in enumerate(array) {
        if elem === value {
            return index
        }
    }
    return nil
}

and then find and remove from the array with

func removeDelegate(delegate:LocationManagerDelegate) {
    if let index = findIdenticalObject(_delegates, delegate) {
        _delegates.removeAtIndex(index)
    }
}


回答2:

The arguments to contains must implement the Equatable protocol since it is defined as:

public func contains<T:Equatable>(left:[T], right:T) -> Bool

Since there's no way to indicate that LocationManagerDelegate implements Equatable, I don't think you can use it. The obvious attempt would be:

protocol LocationManagerDelegate : Equatable {
    ...
}

But that will fail when you try to declare the array because Equatable uses Self.

The best option I can come up with is:

func removeDelegate(delegate:LocationManagerDelegate) {
    _delegates = filter(_delegates) { return $0 !== delegate }
}


回答3:

protocol LocationManagerDelegate {
    // ...
    var index_delegate:Int?{get set}
}



class LocationManager {
    private var delegates:[LocationManagerDelegate] = []

    func add(delegate: LocationManagerDelegate?){
        if let d = delegate {
            self.delegates.append(d)
            let index = self.delegates.count - 1
            self.delegates[index].index_delegate = index
            }
    }

    func remove(delegate: LocationManagerDelegate) {
        delegates = delegates.filter({ return $0.index_delegate != delegate.index_delegate })
    }

}