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
andlast
properties for safe access to the value of the array’s first and last elements. If the array is empty, these properties arenil
....
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
Create Skscene Subclasses Programmatically, Without Size Info
Scenekit - Animation with Dae File Format
How to Convert Nsnull to Nil in Swift
Detail View Is Not Updated When the Model Is Updated (Using List) Swiftui
How to Represent Magnitude for Mass in Swift
Convert Bar Chart to a Grouped Bar Chart with Danielgindi/Ios-Charts and Swift
How to Determine If a Variable Passed in Is Reference Type or Value Type
How to Get Next Case of Enum(I.E. Write a Circulating Method) in Swift 4.2
How to Create Text File for Writing
Deploy App with Pre-Populated Core Data
Does Swift Have an Implicit Object Initializer, Like in C#
Nsnumberformatter:Show 'K' Instead of ',000' in Large Numbers
Get Url to a Local File with Spm (Swift Package Manager)
How Use and Run Swift 2.3 on Command Line