Swift: Better way to remove a specific Object from an array?

Use firstIndex(where:) (previously called index(where:) in Swift 4.1 and earlier) to search the array for your object using the predicate { $0 === objectToRemove }, then call remove(at:) on the array to remove it:

if let idx = objectArray.firstIndex(where: { $0 === objectToRemove }) {
    objectArray.remove(at: idx)
}

This allows you to search for your object whether it is Equatable or not.


If you are coding with Xcode 10.0+ beta (Swift 4.2 or later) you can use the new method removeAll(where:)

mutating func removeAll(where predicate: (Element) throws -> Bool) rethrows

Discussion: Use this method to remove every element in a collection that meets particular criteria. Complexity: O(n), where n is the length of the collection.

This example removes all the odd values from an array of numbers:

Swift 5.2 or later

extension BinaryInteger {
    var isEven: Bool { isMultiple(of: 2) }
    var isOdd: Bool { !isMultiple(of: 2) }
}

var numbers = [5, 6, 7, 8, 9, 10, 11]
numbers.removeAll(where: \.isOdd) // numbers == [6, 8, 10]
numbers

In your case make sure MyCustomObject conforms to Equatable

objectArray.removeAll(where: { $0 == objectToRemove })

or use one of its properties that does conform to it as the predicate (i.e id: Int):

objectArray.removeAll(where: { $0.id == idToRemove })

Note: If you are not using Xcode 10.0+ beta (Swift 4.2) you can implement your own removeAll(where:) method as you can see in this answer.


Implementing a removeFirst(where:) and removeLast(where:) to avoid iterating the whole Collection as mentioned in comments by @vacawama

Swift 4.1

extension RangeReplaceableCollection  {
    @discardableResult
    mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows -> Element?  {
        guard let index = try index(where: predicate) else { return nil }
        return remove(at: index)
    }
}

extension RangeReplaceableCollection where Self: BidirectionalCollection {
    @discardableResult
    mutating func removeLast(where predicate: (Element) throws -> Bool) rethrows -> Element? {
        guard let index = try indices.reversed().first(where: {
            try predicate(self[$0])
        }) else { return nil }
        return remove(at: index)
    }
}

Swift 4.2 or later (as suggested by @Hamish)

extension RangeReplaceableCollection {
    @discardableResult
    mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows -> Element? {
        guard let index = try firstIndex(where: predicate) else { return nil }
        return remove(at: index)
    }
}

extension RangeReplaceableCollection where Self: BidirectionalCollection {
    @discardableResult
    mutating func removeLast(where predicate: (Element) throws -> Bool) rethrows -> Element? {
        guard let index = try lastIndex(where: predicate) else { return nil }
        return remove(at: index)
    }
}

You can also check this post for a remove(while:), removeLast(while:) and dropLast(while:) method implementations.

Tags:

Arrays

Swift