In my app I'm overriding array's subscript to get an additional functionality when array contains my elements (which are adhering to my ValueElement protocol):
protocol ValueElement {
...
}
extension Array where Element: ValueElement {
subscript(_ index: Int) -> Element {
get {
print("get element at \(index)")
// do other stuff
// finally call through original subscript
}
set {
print("set \(newValue) at \(index)")
// do other stuff
// finally call through the original subscript
}
}
}
to do the "call through the original subscript" part at the moment I've resorted to having a small helper static library as part of my project that exposes original array subscript renamed:
// MyHelperLibrary.swift
extension Array {
public subscript(elementAt index: Int) -> Element {
get { self[index] }
set { self[index] = newValue }
}
}
so I can do:
import MyHelperLibrary
extension Array where Element: ValueElement {
subscript(_ index: Int) -> Element {
get {
print("get element at \(index)")
// do other stuff
// finally call through original subscript:
return self[elementAt: index]
}
set {
print("set \(newValue) at \(index)")
// do other stuff
// finally call through original subscript:
self[elementAt: index] = newValue
}
}
}
Is there a cleaner way to achieve the same without using a helper library?
I attempted this:
extension Array where Element: ValueElement {
subscript(_ index: Int) -> Element {
get {
print("get element at \(index)")
// do other stuff
// finally call through original subscript:
return (self as [Any])[index] as! Element // O(N)
}
set {
print("set \(newValue) at \(index)")
// do other stuff
// finally call through original subscript:
// (self as! [Any])[index] = newValue // 🛑 Cannot assign to immutable expression of type 'Any'
var newArray = self as [Any] // O(N)
newArray[index] = newValue
self = newArray as! [Element] // O(N)
}
}
}
But that's much worse as it now has O(N) instead of O(1) time complexity for both getter and setter.
This feels like an XY problem. This will only affect code where the element type is statically known, not in generic code, so this probably isn’t the solution you want.
Why do you want to hook into the array’s subscript?
I don't think there is any general solution for hooking into members of existing types or accessing the original functionality from within a shadowed member. You probably just have to be lucky to find something that works case by case. Maybe something like this for Dictionary:
extension Dictionary {
subscript(_ key: Key) -> Value? {
get {
print("get value for key \(key)")
return index(forKey: key).map { self[$0].value }
}
set {
print("set \(String(describing: newValue)) for key \(key)")
if let newValue { updateValue(newValue, forKey: key) } else { removeValue(forKey: key) }
}
}
}
I was thinking about turning this into an advantage:
i.e. do some cast / etc that would make static type information forgotten - and thus the original implementation called instead of my override. Is that possible, and, as importantly, is it possible to do it without incurring O(N) complexity?
Other than that, the case by case workaround is ok for me. And there's the above static library approach which'd work if there's no good type specific solution. e.g. if I also want to hook into client calling through removeValue, updateValue and subscript(_ index: Dictionary.Index) themselves, I'd need to find either another ad-hoc solution or do that via the static library, unless there's a nicer general solution that works in general case.