20

I have a custom class in Swift and I'd like to use subscripting to access its properties, is this possible?

What I want is something like this:

class User {
    var name: String
    var title: String

    subscript(key: String) -> String {
        // Something here
        return // Return the property that matches the key…
    }

    init(name: String, title: String) {
        self.name = name
        self.title = title
    }
}

myUser = User(name: "Bob", title: "Superboss")
myUser["name"] // "Bob"

Update: The reason why I'm looking for this is that I'm using GRMustache to render from HTML templates. I'd like to be able to just pass my model object to the GRMustache renderer…

GRMustache fetches values with the keyed subscripting objectForKeyedSubscript: method and the Key-Value Coding valueForKey: method. Any compliant object can provide values to templates.

https://github.com/groue/GRMustache/blob/master/Guides/view_model.md#viewmodel-objects

Matias Korhonen
  • 886
  • 2
  • 8
  • 21

6 Answers6

21

This is a bit of a hack using reflection. Something along the lines of the following could be used.

protocol PropertyReflectable { }

extension PropertyReflectable {
    subscript(key: String) -> Any? {
        let m = Mirror(reflecting: self)
        for child in m.children {
            if child.label == key { return child.value }
        }
        return nil
    }
}

struct Person {
    let name: String
    let age: Int
}

extension Person : PropertyReflectable {}

Then create a Person and access it's keyed properties.

let p = Person(name: "John Doe", age: 18)

p["name"] // gives "John Doe"
p["age"] // gives 18

You could modify the subscript to always return an interpolated string of the property value.

Benzi
  • 2,439
  • 18
  • 25
  • A nice solution but it should be noted that this (and reflection in general) does not work for computed properties in Swift – WongWray Aug 13 '20 at 17:25
11

Adding some syntax sugar to Benzi's answer:

protocol PropertyReflectable { }

extension PropertyReflectable {
    subscript(key: String) -> Any? {
        let m = Mirror(reflecting: self)
        return m.children.first { $0.label == key }?.value
    }
}

struct Person: PropertyReflectable {
    let name: String
    let age: Int
}

Then create a Person and access it's keyed properties.

let p = Person(name: "John Doe", age: 18)

p["name"] // gives "John Doe"
p["age"] // gives 18
Justin Oroz
  • 614
  • 8
  • 14
10

Using valueForKey should enable you to access properties using their names. Be sure that you're working with a object that inherit NSObject

class people: NSObject {
    var age: NSString = "44"
    var height: NSString = "153"
}

let person:people = people()

let stringVariable = "age"

person.valueForKey("age")
// Print "44"

person.valueForKey("\(stringVariable)")
// Print "44"
shim
  • 9,289
  • 12
  • 69
  • 108
Bruno Morais
  • 1,029
  • 11
  • 12
5

(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

Gwendal Roué
  • 3,949
  • 15
  • 34
5

Shim's answer above doesn't work anymore in Swift 4. There are two things you should be aware of.

First of all, if you want to use value(forKey:) function, your class must inherit NSObject.

Secondly, since Objective-C doesn't know anything about value type, you have to put the @objc keyword in front of your value type properties and Swift will do the heavy-lifting for you.

Here is the example:

import Foundation

class Person: NSObject {
    @objc var name: String = "John Dow"
    @objc var age: Int = 25
    @objc var height: Int = 180

    subscript(key: String) -> Any? {
        return self.value(forKey: key)
    }
}

let person: Person = Person()

person["name"] // "John Dow"
person["age"] // 25
person["height"] // 180
Frank Wang
  • 114
  • 2
  • 6
1

I suppose you could do:

class User {
    let properties = Dictionary<String,String>()

    subscript(key: String) -> String? {
        return properties[key]
    }

    init(name: String, title: String) {
        properties["name"] = name
        properties["title"] = title
    }
}

Without knowing your use case I would strongly advise against doing this.

Another approach:

class User {
    var name : String
    var title : String

    subscript(key: String) -> String? {
        switch key {
            case "name" : return name
            case "title" : return title
            default : return nil
        }
    }

    init(name: String, title: String) {
        self.name = name
        self.title = title
    }
}

It might be worth noting that Swift doesn't appear to currently support reflection by names. The reflect function returns a Mirror whose subscript is Int based, not String based.

hpique
  • 119,096
  • 131
  • 338
  • 476
  • 1
    I'm answering the question because I think it teaches about subscripting, switch and maybe a little bit about reflection (or the lack thereof). That said, I don't recommend doing any of the above. – hpique Jun 10 '14 at 11:03