David.Chu.ca David.Chu.ca - 1 month ago 15
Swift Question

NSExpression Subquery usage

Swift 3 introduced a new NSPexpresion type Subquery expression. I tried to use it in Playground but could not get it to work.

First I created a class in Sources folder in MyPlayground:

// example classes
public class Person {
public let name: String
public let birthYear: Int

init(name pName: String, birthYear year : Int) {
name = pName
birthYear = year
}
}

public class Parent: Person {
public var children: [Person] = [Person]()
override init(name pName: String, birthYear year : Int) {
super.init(name: pName, birthYear: year)
}
}

public class TestSubquery {
// Method to get a Parent
public class func getParent() -> Parent {
let p = Parent(name: "Bob", birthYear: 1997)
let c = Person(name: "Tonny", birthYear: 2010)
p.children.append(c)
return p
}
}


In MyPlayground, the collection expression is created as e1 (I tried both aggregate and format expression, all are collection expression but failed in late part):

let p = TestSubquery.getParent()
print("\(p.children.count)") // 1
let e1 = NSExpression(forAggregate: p.children)
// let e1 = NSExpressoin(format: "%@", p.children)
print("e1: \(e1)")
//e1: {MyPlayground_Sources.Person}


Then a subquery expression as e2 is created:

let e2 = NSExpression(forSubquery: e1, usingIteratorVariable: "x", predicate: "$x.birthYear > 0")
print("\(e2)")
// SUBQUERY({MyPlayground_Sources.Person}, $x, $x.birthYear > 0)


Finally when I tried to get result from expression, error occurred:

let expValue = e2.expressionValue(with: nil, context: nil)
// Error


The error message is as follows:

*** NSForwarding: warning: object 0x6080000547f0 of class 'MyPlayground_Sources.Person' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector -[MyPlayground_Sources.Person expressionValueWithObject:context:]


It looks like key-value cannot be found. I am not sure what is wrong to build Subquery expression.

Greatly appreciate any help!

Answer

After days struggling, I think I got a solution!

First, the class in the collection, for example Person in an array, has to be inherited from NSObject.

public class Person : NSObject {

Secondly, according to Apple's Developer documentation

enter image description here

That is, the third parameter should be NSPredicate type (even though String is of Any?). The updated e2 should be like:

let predicate = NSPredicate(format: "$x.birthYear > 0")
let e2 = NSExpression(forSubquery: e1, usingIteratorVariable: "x", predicate: predicate)
let expValue = e2.expressionValue(with: nil, context: nil)

With those changes, the Subquery expression works like charm.