Skip to content

Instantly share code, notes, and snippets.

@indragiek
Last active August 29, 2015 14:07
Show Gist options
  • Save indragiek/da60a77cb824c104129f to your computer and use it in GitHub Desktop.
Save indragiek/da60a77cb824c104129f to your computer and use it in GitHub Desktop.
Implementation of -valueForKeyPath: that supports indexed subscripts, implemented in Swift.
//
// KeypathExtensions.swift
//
// Created by Indragie on 10/3/14.
// Copyright (c) 2014 Indragie Karunaratne. All rights reserved.
//
import Foundation
let KeyRegex = NSRegularExpression(pattern: "^(\\w[\\w\\d]*)?(\\[\\d+\\])*$", options: NSRegularExpressionOptions(0), error: nil)
let SubscriptRegex = NSRegularExpression(pattern: "\\[\\d+\\]", options: NSRegularExpressionOptions(0), error: nil)
private struct KeypathComponent: Printable {
let key: String
let subscriptIndices: [Int]
var description: String {
return "KeypathComponent: key=\(key), subscriptIndices=\(subscriptIndices)"
}
init(key: String, subscriptIndices: [Int] = []) {
self.key = key
self.subscriptIndices = subscriptIndices
}
}
public extension NSObject {
public func ind_valueForKeyPath(keypath: NSString) -> AnyObject? {
let keys = keypath.componentsSeparatedByString(".") as [NSString]
let components = keys.map { (k) -> KeypathComponent in
let fullRange = NSMakeRange(0, k.length)
let match = KeyRegex.firstMatchInString(k, options: NSMatchingOptions(0), range: fullRange)
if match == nil {
NSException(name: "INDKeypathException", reason: "Invalid keypath format", userInfo: ["keypath": keypath, "key": k]).raise()
}
var subscriptIndices = [Int]()
var keyRange = fullRange
SubscriptRegex.enumerateMatchesInString(k, options: NSMatchingOptions(0), range: fullRange) { (result, _, _) in
let range = result!.range
let delta: Int = keyRange.location + keyRange.length - range.location // Using NSMaxRange() crashes the compiler
if delta > 0 {
keyRange = NSMakeRange(keyRange.location, keyRange.length - delta)
}
let sub = k.substringWithRange(NSMakeRange(range.location + 1, range.length - 2))
subscriptIndices.append(sub.toInt()!)
}
let key = k.substringWithRange(keyRange)
return KeypathComponent(key: key, subscriptIndices: subscriptIndices)
}
var object: AnyObject? = self
for c in components {
if object == nil { break }
if countElements(c.key) > 0 {
object = object!.valueForKey(c.key)
}
for index in c.subscriptIndices {
object = object!.objectAtIndexedSubscript(index)
}
}
return object
}
}
//
// KeypathExtensionsTests.swift
//
// Created by Indragie on 10/3/14.
// Copyright (c) 2014 Indragie Karunaratne. All rights reserved.
//
import XCTest
class KeypathTestClass : NSObject {
var foo: NSDictionary {
return [
"a": [
"b": "c"
],
"d": [
["e",
["f": ["g"] as NSArray]
]
] as NSArray
]
}
}
class KeypathExtensionsTests: XCTestCase {
func testSubscriptsWithoutKey() {
let array: NSArray = [1, [2, [3]]]
XCTAssertEqual(array.ind_valueForKeyPath("[1][1][0]") as Int, 3)
}
func testKeypathWithoutSubscripts() {
let obj = KeypathTestClass()
XCTAssertEqual(obj.ind_valueForKeyPath("foo.a.b") as String, "c")
}
func testKeypathWithSubscripts() {
let obj = KeypathTestClass()
XCTAssertEqual(obj.ind_valueForKeyPath("foo.d[0][1].f[0]") as String, "g")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment