orchetect / OTCore

Useful extensions on Swift standard library types.
MIT License
7 stars 2 forks source link

NSArray: add [safe:] subscripts #1

Open orchetect opened 3 years ago

orchetect commented 3 years ago

Objective

On NSArray and NSMutableArray, add a custom subscript that duplicates the [safe:] subscript that OTCore already implements on standard Swift collections.

Interface

extension NSArray {
    open subscript(safe index: Int) -> Any? { get }
}

extension NSMutableArray {
    open subscript(safe index: Int) -> Any? { get set }
}

Implementation

  1. The implementation checks that the passed index is safe (it is not out-of-bounds).
  2. If the index is valid, then the element at that index is returned.
  3. If the index is not valid, then nil is returned.

Problem

I could not find a way to make the compiler happy when both subscripts use the same label "safe:". The nature of Objective-C objects is making this elusive to figure out.

The following implementation does function as expected (with unique subscript labels) but the goal is to use the same labels.

extension NSArray {
    open subscript(safe index: Int) -> Any? {
        (0..<count).contains(index) ? self[index] : nil
    }
}

extension NSMutableArray {
    open subscript(safeMutable index: Int) -> Any? {
        get {
            (0..<count).contains(index) ? self[index] : nil
        }
        set {
            guard (0..<count).contains(index) else { return }
            self[index] = newValue!
        }
    }
}

As soon as you make the subscript labels the same, it will not compile.

extension NSArray {
    open subscript(safe index: Int) -> Any? { get }
}

extension NSMutableArray {
    // Compiler Error: "Overriding non-@objc declarations from extensions is not supported"
    open subscript(safe index: Int) -> Any? { get set }
}

However, making both subscripts @objc and having the NSMutableArray declared override will allow the code to compile, but the functions are never actually executed when the subscript is called. Instead, the underlying subscript executes (which of course produces EXC_BAD_INSTRUCTION when an index is out-of-bounds since that is default behavior of NSArray and NSMutableArray.

extension NSArray {
    @objc open subscript(safe index: Int) -> Any? { get }
}

extension NSMutableArray {
    @objc open override subscript(safe index: Int) -> Any? { get set }
}

Next Steps

I believe the goal of implementing this with identical subscript labels [safe:] for both NSArray and NSMutableArray is possible, but it will require more research and assistance.

orchetect commented 3 years ago

Currently, these are implemented in the library as:

NSArray:

let array = NSArray()

let getVal = nsArray[safe: 1]

NSMutableArray:

let array = NSMutableArray()

// both getters work ([safe:] is inherited from NSArray):
let getVal = array[safe: 1]
let getVal = array[safeMutable: 1]

// setter:
array[safeMutable: 1] = newValue

But the goal is to unify them into a single [safe:] subscript for both NSArray (get) and NSMutableArray (get and set).