Last active
August 29, 2015 14:07
-
-
Save indragiek/da60a77cb824c104129f to your computer and use it in GitHub Desktop.
Implementation of -valueForKeyPath: that supports indexed subscripts, implemented in Swift.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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