Search code examples
swiftnspredicate

Different NSPredicate behaviour between Simulator and device


I know I'm doing the 'wrong' thing here, but it worked in all situations except in the Simulator when targeting an iPad Air or Retina - it works on the Simulator for different devices or a supported physical device (6S and Retina tested), but fails on iPad Air or Retina when run in the Simulator.

Latest version of Realm (0.97) and iOS (9.2) and XCode (7.2). To reproduce the problem, here is a VERY contrived example...

import UIKit
import RealmSwift

class MyObject: Object {
    dynamic var x: Int64 = 0
    dynamic var y: Int64 = 0
}

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

        print("Realm path: \(Realm.Configuration.defaultConfiguration.path!)")

        let realm = try! Realm()

        try! realm.write({
            realm.deleteAll()
        })

        let myObject = MyObject()
        myObject.x = 1
        myObject.y = 2

        try! realm.write({
            realm.add(myObject)
        })

        let findX: Int64 = 1
        let findY: Int64 = 2

        let p1 = NSPredicate(format: "x == %d and y == %d", findX, findY)
        let p2 = NSPredicate(format: "x == %d", findX)
        let p3 = NSPredicate(format: "x == %lld and y == %lld", findX, findY)

        print("Test 1: Fail on iPad retina in Simulator: \(realm.objects(MyObject).filter(p1).count)")
        print("Test 2: Pass on all platforms: \(realm.objects(MyObject).filter(p2).count)")
        print("Test 3: Pass on all platforms: \(realm.objects(MyObject).filter(p3).count)")

        return true
    }

}

When you run this, all of these tests "should" print "1" (and yes, test 3 is the most correct). But, for me at least, the first test returns "0" if run in the Simulator and targeting an iPad Air or Retina. And they work on physical devices.

I know the third test is how it should be coded, but (1) why does the second test work then; and (2) why does this only occur in the Simulator for these specific devices? (And I'll have you know it took quite a while to work out the root cause!)


Solution

  • Your problem is the format string that you use to construct the NSPredicate. You can see this by simplifying your code to the following:

    let findX: Int64 = 1
    let findY: Int64 = 2
    
    let p1 = NSPredicate(format: "x == %d and y == %d", findX, findY)
    let p2 = NSPredicate(format: "x == %lld and y == %lld", findX, findY)
    
    print(p1)
    print(p2)
    

    In a 32-bit simulator this prints:

    x == 1 AND y == 0
    x == 1 AND y == 2
    

    In a 64-bit simulator it prints:

    x == 1 AND y == 2
    x == 1 AND y == 2
    

    This shows that using the format string x == %d and y == %d results in a predicate that doesn't express the query that you intend. The reason this happens is that the format specifiers for p1 do not match the types of the arguments you are passing. In particular, the %d format string expects you to pass an Int but you're passing an Int64. By using %lld it correctly looks for the 64-bit values that you are passing.