chaimp
chaimp

Reputation: 17847

Is it possible in Swift to execute code at runtime from a string?

I have a list of items that have conditions associated with them. I want to store this list of items and their conditions in a plist rather than hardcode them into a .swift file.

The only problem with that is that there needs to a function associated with each item to check a condition. Here is what it might look like hardcoded:

let myJobStep1 = JobStep(heading: "My Heading", description: "This is the description", warningText: "", condition_check: { () -> Bool in
    return (self.trayColor == .Blue) || (self.trayColor == .Red)
})

let myJobStep2 = JobStep(heading: "My Heading", description: "Another description", warningText: "", condition_check: { () -> Bool in
    return (self.trayColor == .Green)
})

The question is how to encapsulate the function that is checking conditions in a string that can be in a plist file.

Thanks!

Upvotes: 3

Views: 2521

Answers (2)

David Berry
David Berry

Reputation: 41226

The closest you're going to come is NSPredicate and/or NSExpression which give you a limited ability to dynamically evaluate expressions given as strings.

enum Colors : Int {
    case Red = 1
    case Green = 2
    case Blue = 3
}

class Line : NSObject {
    var lineColor : Int

    init(lineColor:Colors) {
        self.lineColor = lineColor.rawValue
    }
}

let red = Line(lineColor: .Red)
let green = Line(lineColor: .Green)

let basic = NSPredicate(format: "self.lineColor == $Red")
let test = basic.predicateWithSubstitutionVariables(["Red":Colors.Red.rawValue])

test.evaluateWithObject(red)       // true
test.evaluateWithObject(green)     // false

Since NSExpression is based on Objective-C and Key Values, there are some restrictions:

  1. The object being evaluated must be derived from NSObject.
  2. Properties must be automatically translatable to AnyObject, hence we store the int and not the Color. Note that you could probably handle this a little differently by using a derived property.
  3. The substitution dictionary must be [String:AnyObject] so you'll have to use the enum raw value instead of the enum value itself.

Upvotes: 3

rickster
rickster

Reputation: 126127

This sounds like a job for predicates!

The functional programming constructs in Swift — map, filter, shorthand closures, etc. — are great tools for when you want to express relationships between data in imperative code. But they aren't the only way to look at such problems.

In particular, it's often useful to be able to express relationships between data as data:

  • If you're loading a bunch of data model objects from a simple file representation, you can encode their relationships as such, too.

  • If you're encoding data relationships as data, you can load / update / download them at runtime, instead of changing code and shipping a new app binary.

  • If you're dealing with a backend database / web service / RDBMS / ORM (CloudKit, Core Data, etc), you want a way to express things like filters, queries, sort orders, and relationships that you can pass to the backend and let the big expensive data-crunching operations happen there.

This is what NSPredicate (and, relatedly, NSSortDescriptor) are for. Unlike a filter / comparison closure, the essential logic associated with a predicate can be data, not code. So you can have a data source that looks like this:

[
    "heading": "My Heading",
    "description": "This is the description",
    "condition": "trayColor == \"Blue\" || trayColor == \"Red\""
]

Then you can create predicates from your data source:

let predicate = NSPredicate(format: item.condition)

And use them to test conditions on individual objects, filter collections, etc:

predicate.evaluateWithObject(item)
(items as NSArray).filteredArrayUsingPredicate(predicate)

Edit: @DavidBerry beat me to the punch. This is more of a "why" / high-level answer, but his has some good details on bits like setting up a mapping between enum cases, data-file contents, and predicate formats.

Upvotes: 1

Related Questions