WeakArray for the Last Time (Swift 4.2)

I am obsessed with weak arrays. If you're trying to use multiple "protocolized" delegates, you need a weakly-held array of delegates.

This one started with this great article and expanded from there. Big thanks to Varindra Hart for the wise counsel as always.

@objc public protocol Equalable: class {
    @objc func isEqual(_ object: Any?) -> Bool
}

/// Store AnyObject subclasses weakly
/// * Note: if you wish to use a protocol, it must:
///   - be marked with `@objc`
///   - have all methods marked with `@objc`
///   - refine Equalable
public struct WeakArray<Element: Equalable> {
    private var items: [WeakBox<Element>] = []

    public init(_ elements: [Element]? = nil) {
        guard let elements = elements else { return }
        items = elements.map { WeakBox($0) }
    }

    public mutating func append(_ newElement: Element) {
        let box = WeakBox(newElement)
        items.append(box)
    }

    public mutating func remove(_ element: Element) {
        items.removeAll { item in
            return item.unbox?.isEqual(element) ?? false
        }
    }

    public var unboxed: [Element] {
        let filtered = items.filter { $0.unbox != nil }
        return filtered.compactMap { $0.unbox }
    }

    public var boxedCount: Int {
        return items.count
    }
}

extension WeakArray: Collection {
    public var startIndex: Int { return items.startIndex }
    public var endIndex: Int { return items.endIndex }

    public subscript(_ index: Int) -> Element? {
        return items[index].unbox
    }

    public func index(after idx: Int) -> Int {
        return items.index(after: idx)
    }
}

private final class WeakBox<T: Equalable> {
    weak var unbox: T?
    init(_ value: T) {
        unbox = value
    }
}

GitHub Gist

The need to have your protocol conform to @objc is a bug in Swift. See the StackOverflow question about this.