Access Properties via Subscripting in Swift

Access properties via subscripting in Swift

(GRMustache author here)

Until a swift-oriented Mustache library is out, I suggest having your classes inherit from NSObject (so that they have the valueForKey: method). GRMustache will then fetch values with this method.

In case this would still not work (blank values in the rendering), you may try to disable GRMustache security features (see https://github.com/groue/GRMustache/blob/master/Guides/security.md#disabling-safe-key-access)

Should you experience any other trouble, please open an issue right into the repository: https://github.com/groue/GRMustache/issues

EDIT February 2, 2015: GRMustache.swift is out: http://github.com/groue/GRMustache.swift

Swift: Accessing properties in a dictionary of structs

For arrays, the subscript operator returns the value.

var i = arr[0] // <- i is of type SceneStruct

For dictionaries, the subscript operator returns an optional.

var x = dict["one"] // <- x is of type SceneStruct? (Optional< SceneStruct>)

The reason is arr[100] throws an exception because the array is out of bounds. Whereas dict["invalid"] returns nil the lookup failed.

You can workaround this difference in a few ways.

Evaluate with a default value

var defaultValue = SceneStruct(number: 0)
var x = dict["one"] ?? defaultValue // provide a default value if dict["one"] is nil.
var y = x.number

Conditional evaluation

if let x = dict["one"] { // conditionally set x
var y = x.number
} else {
// dict["one"] is nil
}

Forced unwrapped

var x = dict["one"]! // force unwrap dict["one"], this will throw an exception if dict["one"] is nil. 
var y = x.number

How to access property or method from a variable?

You can do it, but not using "pure" Swift. The whole point of Swift (as a language) is to prevent that sort of dangerous dynamic property access. You'd have to use Cocoa's Key-Value Coding feature:

self.setValue(value, forKey:field)

Very handy, and it crosses exactly the string-to-property-name bridge that you want to cross, but beware: here be dragons.

(But it would be better, if possible, to reimplement your architecture as a dictionary. A dictionary has arbitrary string keys and corresponding values, and thus there is no bridge to cross.)

PropertyWrapper subscript is not called. WHY?

Property wrappers merely provide an interface for the basic accessor methods, but that’s it. It’s not going to intercept subscripts or other methods.

The original property wrapper proposal SE-0258 shows us what is going on behind the scenes. It contemplates a hypothetical property wrapper, Lazy, in which:

The property declaration

@Lazy var foo = 1738

translates to:

private var _foo: Lazy<Int> = Lazy<Int>(wrappedValue: 1738)
var foo: Int {
get { return _foo.wrappedValue }
set { _foo.wrappedValue = newValue }
}

Note that foo is just an Int computed property. The _foo is the Lazy<Int>.

So, in your a["key"] = 5 example, it will not use your property wrapper’s subscript operator. It will get the value associated with a, use the dictionary’s own subscript operator to update that value (not the property wrapper’s subscript operator), and then it will set the value associated with a.

That’s all the property wrapper is doing, providing the get and set accessors. E.g., the declaration:

@AtomicDictionary var a: [String: Int]

translates to:

private var _a: AtomicDictionary<String, Int> = AtomicDictionary<String, Int>(wrappedValue: [:])
var a: [String: Int] {
get { return _a.wrappedValue }
set { _a.wrappedValue = newValue }
}

Any other methods you define are only accessible through _a in this example, not a (which is just a computed property that gets and sets the wrappedValue of _a).


So, you’re better off just defining a proper type for your “atomic dictionary”:

public class AtomicDictionary<Key: Hashable, Value> {
private var wrappedValue: [Key: Value]

private let queue = DispatchQueue(label: "atomicDictionary.\(UUID().uuidString)", attributes: .concurrent)

init(_ wrappedValue: [Key: Value] = [:]) {
self.wrappedValue = wrappedValue
}

public subscript(key: Key) -> Value? {
get {
queue.sync {
wrappedValue[key]
}
}

set {
queue.async(flags: .barrier) {
self.wrappedValue[key] = newValue
}
}
}
}

And

let a = AtomicDictionary<String, Int>()

That gives you the behavior you want.


And if you are going to supply CustomDebugStringConvertible conformance, make sure to use your synchronization mechanism there, too:

extension AtomicDictionary: CustomDebugStringConvertible {
public var debugDescription: String {
queue.sync { wrappedValue.debugDescription }
}
}

All interaction with the wrapped value must be synchronized.


Obviously you can use this general pattern with whatever synchronization mechanism you want, e.g., the above reader-writer pattern, GCD serial queue, locks, actors, etc. (The reader-writer pattern has a natural appeal, but, in practice, there are generally better mechanisms.)


Needless to say, the above presumes that subscript-level atomicity is sufficient. One should always be wary about general purpose thread-safe collections as often the correctness of our code relies on a higher-level of synchronization.

In Swift, can one use a string to access a struct property?

I don't think string-based access like this is good Swift style. vadian shows how it can be done, but to dynamically get and set members like this, it would be best to use the built-in keypath functionality:

let redChannel = pixel[keyPath: \.red]
pixel[keyPath: \.green] = 0xB5

Another option (more relevant prior to Swift 4) would be to use an enum to define the keys:

enum Component
{
case red
case green
case blue
case alpha
}

Then adapt the subscript function that vadian demonstrated to accept Pixel.Component instead of String.

This has a significant advantage in that you can no longer pass an invalid key.

Based on your definition:

public extension Pixel
{
public enum Component
{
case red, blue, green, alpha
}

public subscript(key: Component) -> UInt8
{
get
{
switch key {
case .red: return self.red
case .green: return self.green
case .blue: return self.blue
case .alpha: return self.alpha
}
}
set
{
switch key {
case .red: self.red = newValue
case .green: self.green = newValue
case .blue: self.blue = newValue
case .alpha: self.alpha = newValue
}
}
}
}

var pxl = Pixel(value: 0xFEEDFACE, red: 0xFE, green: 0xED, blue: 0xFA, alpha: 0xCE)
let redChannel = pxl[.red]
print(redChannel)
pxl[.green] = 0xB5
print(pxl)

Beginner: When working with arrays, why does this work but not this?

The issue is that with an array, the subscript operator does not return an optional, so it makes no sense to use an unwrapping construct like if let to unwrap it.

Try let foo = colorsArray[42], where the index is out of bounds. It doesn’t return nil, but rather will just crash with an “subscript out of range” error. As the documentation says (emphasis added):

Use the first and last properties for safe access to the value of the array’s first and last elements. If the array is empty, these properties are nil.

...

You can access individual array elements through a subscript. The first element of a nonempty array is always at index zero. You can subscript an array with any integer from zero up to, but not including, the count of the array. Using a negative number or an index equal to or greater than count triggers a runtime error.

So, the last provides safe access, returning an optional, but the subscript operator doesn’t return an optional, but also doesn’t provide safe access if you supply an invalid index.



Related Topics



Leave a reply



Submit