NSPredicate - Unable to generate SQL for predicate, I wonder why? - objective-c

I have already solved my problem [Blindly] without understanding root cause. But I would rather understand a concept from a professional. So could you please tell me why below identical code one works but another doesn't.
Code 1: Doesn't work
//Above code omitted...
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"gender == m"]; //NOTICE HERE
[request setPredicate:predicate];
NSError *error = nil;
self.people = [self.managedObjectContext executeFetchRequest:request error:&error];
//Below code omitted...
Code 2: Does work
//Above code omitted...
NSString *type = #"m";
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"gender == %#",type]; //NOTICE HERE
[request setPredicate:predicate];
NSError *error = nil;
self.people = [self.managedObjectContext executeFetchRequest:request error:&error];
//Below code omitted...
Forgot to tell about what error I got, I got SIGABRT on below line, When I executed Code 1.
self.people = [self.managedObjectContext executeFetchRequest:request error:&error];
And one more thing, in GCC error was it cannot format predicate because of "gender == m".
Enlighten me!!
Thanks

See the predicate programming guide (heading "Literals"). You can use literals in your string but you have to enclose them in quotes, so
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"gender == 'm'"];
Would have worked. When predicateWithFormat adds in the argument, it knows it is a string. When you just have m in there, it doesn't know what to do with it, hence the error.

example with swift
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
let fetchRequest = NSFetchRequest(entityName:"Words")
fetchRequest.predicate = NSPredicate(format: "letter == '\(letter)'")
var error: NSError?
let fetchedResults =
managedContext.executeFetchRequest(fetchRequest,
error: &error) as? [NSManagedObject]
if let results = fetchedResults {
println(results)
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}

Related

Objective-C converting to Swift 3.1 getting error 'Any' is not convertible

I'm finally attempting to convert one of my Objective-C apps to Swift 3.1. I'm also taking a tutorial on Swift to help me out. However, I'm running into the following error when trying to convert a 'for in' loop to Swift that worked successfully in Obj-C. I've posted both the Swift and Objective-C code below and commented the line in Swift where I am getting the error. I am getting the error with
for managedObject: NSManagedObject in myResults{
The error states
'Any' is not convertible to 'MSManageObject'
Any help pointing me in the right direction is greatly appreciated.
//IN SWIFT 3.1
let context: NSManagedObjectContext? =
CoreDataHelper.shared().context
let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
let entity = NSEntityDescription.entity(forEntityName:
"Exhibitors", in: context!)
fetchRequest.entity = entity
var myResults : NSArray = try!
CoreDataHelper.shared().context.fetch(fetchRequest) as NSArray
self.objects = myResults as! [Any]
if !(myResults != nil) || !((myResults.count) != nil) {
print("No Exhibitor objects found to be deleted!")
}
else {
//****Getting error 'Any' is not convertible to 'NSManagedObject'
for managedObject: NSManagedObject in myResults {
if !(managedObject.value(forKey: "fav") == "Yes") {
context?.deleteObject(managedObject)
var error: Error? = nil
// Save the object to persistent store
if !context?.save(error) {
print("Can't Save! \(error) \
(error?.localizedDescription)")
}
print("Exhibitor object deleted!")
}
}
}
//IN OBJECTIVE-C
NSManagedObjectContext *context = [[CoreDataHelper sharedHelper]
context];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]
init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Exhibitors" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *myResults = [[[CoreDataHelper sharedHelper]
context] executeFetchRequest:fetchRequest error:nil];
self.objects = myResults;
if (!myResults || !myResults.count){
NSLog(#"No Exhibitor objects found to be deleted!");
}
else{
for (NSManagedObject *managedObject in myResults) {
if (![[managedObject valueForKey:#"fav"]
isEqualToString:#"Yes"]) {
[context deleteObject:managedObject];
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error,
[error localizedDescription]);
}
NSLog(#"Exhibitor object deleted!");
}
}
}
The main issue is that your are annotating worse types than the compiler infers.
Don't do that. Don't annotate types unless the compiler tells you to do.
For example the managed object context is supposed to be non-optional
let context = CoreDataHelper.shared().context
Your fetch returns [NSManagedObject], never use NSArray and never cast distinct types up to more unspecified like [Any]
Simply write
let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
let entity = NSEntityDescription.entity(forEntityName: "Exhibitors", in: context)
fetchRequest.entity = entity
do { // its always recommended to do a good error handling !!
self.objects = try CoreDataHelper.shared().context.fetch(fetchRequest)
for managedObject in self.objects {
...
}
} catch { print(error) }
If objects is empty the loop will be skipped.
Instead of this:
var myResults : NSArray = try! CoreDataHelper.shared().context.fetch(fetchRequest) as NSArray
Do this:
let myResults : [NSManagedObject] = try! CoreDataHelper.shared().context.fetch(fetchRequest)
You should also use less ! operators, as they may crash your app, try using the guard let pattern or ? operators.

How to update existing object in Core Data?

When I insert new object I do with following code:
NSManagedObjectContext *context = [appDelegate managedObjectContext];
Favorits *favorits = [NSEntityDescription insertNewObjectForEntityForName:#"Favorits" inManagedObjectContext:context];
favorits.title = #"Some title";
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops");
}
How can I update existing object in core data?
Updating is simple as creating a new one.
To update a specific object you need to set up a NSFetchRequest. This class is equivalent to a SELECT statetement in SQL language.
Here a simple example:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Favorits" inManagedObjectContext:moc]];
NSError *error = nil;
NSArray *results = [moc executeFetchRequest:request error:&error];
// error handling code
The array results contains all the managed objects contained within the sqlite file. If you want to grab a specific object (or more objects) you need to use a predicate with that request. For example:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"title == %#", #"Some Title"];
[request setPredicate:predicate];
In this case results contains the objects where title is equal to Some Title. Setting a predicate is equal to put the WHERE clause in a SQL statement.
For further info I suggest you to read the Core Data programming guide and NSFecthRequest class reference.
Core Data Programming Guide
NSFecthRequest Class Reference
Hope it helps.
EDIT (snippet that can be used to update)
// maybe some check before, to be sure results is not empty
Favorits* favoritsGrabbed = [results objectAtIndex:0];
favoritsGrabbed.title = #"My Title";
// save here the context
or if you are not using a NSManagedObject subclass.
// maybe some check before, to be sure results is not empty
NSManagedObject* favoritsGrabbed = [results objectAtIndex:0];
[favoritsGrabbed setValue:#"My title" forKey:#"title"];
// save here the context
In both cases if you do a save on the context, data will be updated.
You have to fetch the object from the context, change the properties you desire, then save the context as you are in your example.
I hope this will help u. as it works for me.
NSMutableArray *results = [[NSMutableArray alloc]init];
int flag=0;
NSPredicate *pred;
if (self.txtCourseNo.text.length > 0) {
pred = [NSPredicate predicateWithFormat:#"courseno CONTAINS[cd] %#", self.txtCourseNo.text];
flag=1;
} else {
flag=0;
NSLog(#"Enter Corect Course number");
}
if (flag == 1) {
NSLog(#"predicate: %#",pred);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]initWithEntityName:#"Course"];
[fetchRequest setPredicate:pred];
results = [[self.context executeFetchRequest:fetchRequest error:nil] mutableCopy];
if (results.count > 0) {
NSManagedObject* favoritsGrabbed = [results objectAtIndex:0];
[favoritsGrabbed setValue:self.txtCourseName.text forKey:#"coursename"];
[self.context save:nil];
[self showData];
} else {
NSLog(#"Enter Corect Course number");
}
}
if you are a swift programmer this can help you :
if you want to delete a NSManagedObject
in my case ID is a unique attribute for entity STUDENT
/** for deleting items */
func delete(identifier: String) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "STUDENT")
let predicate = NSPredicate(format: "ID = '\(identifier)'")
fetchRequest.predicate = predicate
do
{
let object = try context.fetch(fetchRequest)
if object.count == 1
{
let objectDelete = object.first as! NSManagedObject
context.delete(objectDelete)
}
}
catch
{
print(error)
}
}
if you want to update a NSManagedObject :
/** for updating items */
func update(identifier: String,name:String) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "STUDENT")
let predicate = NSPredicate(format: "ID = '\(identifier)'")
fetchRequest.predicate = predicate
do
{
let object = try context.fetch(fetchRequest)
if object.count == 1
{
let objectUpdate = object.first as! NSManagedObject
objectUpdate.setValue(name, forKey: "name")
do{
try context.save()
}
catch
{
print(error)
}
}
}
catch
{
print(error)
}
}
I saw an answer in Objective-C which helped me. I am posting an answer for Swift users -
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let updateCont = appDelegate?.persistentContainer.viewContext
let pred = NSPredicate(format: "your_Attribute_Name = %#", argumentArray : [your_Arguments])
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "your_Entity_Name")
request.predicate = pred
do {
let resul = try updateCont?.fetch(request) as? [NSManagedObject]
let m = resul?.first
m?.setValue(txtName.text, forKey: "your_Attribute_Name_Whose_Value_Should_Update")
try? updateCont?.save()
} catch let err as NSError {
print(err)
}

Xcode 4: Fetch Request Template Variables?

In Xcode 3.X, you were supposed to right-click the whitespace in the fetch request template's predicate editor to specify a variable input rather than a hard-coded predicate.
Where is this in XCode 4? I've held option, right-clicked, option-clicked, etc and cannot figure it out....
I don't think X4 has the variable anymore.
Instead, I think you have to choose an expression and then provide a variable of the form $VARNAME.
For example, given and entity Alpha with an attribute aString, I created a fetch request template bobFetch with an expression of aString == $TESTVAR.
Alpha *a=[NSEntityDescription insertNewObjectForEntityForName:#"Alpha" inManagedObjectContext:self.moc];
a.aString=#"steve";
[self saveContext];
NSDictionary *subVars=[NSDictionary dictionaryWithObject:#"steve" forKey:#"TESTVAR"];
NSFetchRequest *fetchRequest = [self.managedObjectModel fetchRequestFromTemplateWithName:#"bobRequest" substitutionVariables:subVars];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Alpha" inManagedObjectContext:self.moc];
[fetchRequest setEntity:entity];
If logged fetchRequest reports:
<NSFetchRequest: 0x4d17480> (entity: Alpha; predicate: (aString == "steve"); sortDescriptors: ((null)); type: NSManagedObjectResultType; )
... and can then be used normally.
NSError *error = nil;
NSArray *fetchedObjects = [self.moc executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"fetch error = %#",error);
}
NSLog(#"fetchObjects = %#",fetchedObjects);
Kind of clumsy for a graphical environment but it works.
This has been tweaked in Xcode 4. In order to use substitution variables, you need to choose "Expression" from the popup menu (i.e. instead of an attribute name) and you can enter the equivalent like this: name == $SEARCH_NAME
If you were to just enter a $VARIABLE value in the field for each attribute, you'll get the wrong result. In fact, some attributes won't allow that such as Date attributes where you are forced to enter a value.
Of course you can use multiple variables from there on.
Then it's just as before with executing the fetch request:
NSString *searchName = #"Mr Squiggle";
NSDictionary *subs = [NSDictionary dictionaryWithObject:searchName forKey:#"SEARCH_NAME"];
NSManagedObjectModel *model = [self managedObjectModel];
NSFetchRequest *req = [model fetchRequestFromTemplateWithName:#"trainerByName" substitutionVariables:subs];
NSError *error = nil;
NSArray *results = [[self managedObjectContext] executeFetchRequest:req error:&error];
NSLog(#"Found %ld record.", [results count]);
Note you can also do away with the attributes popup and just click the button on the top right of the editor (looks like lines right beside the default grid view button) and just enter your expression straight away. This is a good way of seeing how some things like dates get translated.

Cannot get to NSErrors from unit tests

I am setting up unit tests to run methods that should generate an NSError. For some reason, I can't get to the NSError from the unit tests. I created a sample method to test this, and it still doesn't work. What am I doing wrong?
Implementation file:
- (BOOL)createAnError:(NSError **)error {
NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
[errorDetail setValue:NSLocalizedString(#"This error should be testable", #"")
forKey:NSLocalizedDescriptionKey];
[errorDetail setObject:self
forKey:NSValidationObjectErrorKey];
NSError *cannotDeleteError = [NSError errorWithDomain:#"myErrorDomain"
code:12345
userInfo:errorDetail];
if (*error = nil)
*error = cannotDeleteError;
return NO;
}
Unit Test:
- (void)testNSErrors {
Unit *myObj = [NSEntityDescription insertNewObjectForEntityForName:#"TestObject"
inManagedObjectContext:managedObjectContext];
NSError *error = nil;
STAssertFalse([myObj createAnError:&error], #"This method should return NO");
STAssertEquals([error code], 12345, #"The error code is incorrect. (error = %#)", error);
}
The error I'm seeing in the build results is: error: -[LogicTests testNSErrors] : '0' should be equal to '12345': The error code is incorrect. (error = (null)).
So why is this happening? Am I creating the NSError incorrectly, testing for it incorrectly, or both?
Thank you!
You are not creating the error correctly. In your sample method, you test for if (*error = nil). This is an assignment. What you really mean to say is: if (*error == nil), which uses the equality operator.
Change that and you should get a positive result.

Cocoa Core Data efficient way to count entities

I read much about Core Data.. but what is an efficient way to make a count over an Entity-Type (like SQL can do with SELECT count(1) ...). Now I just solved this task with selecting all with NSFetchedResultsController and getting the count of the NSArray! I am sure this is not the best way...
I don't know whether using NSFetchedResultsController is the most efficient way to accomplish your goal (but it may be). The explicit code to get the count of entity instances is below:
// assuming NSManagedObjectContext *moc
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:entityName inManagedObjectContext:moc]];
[request setIncludesSubentities:NO]; //Omit subentities. Default is YES (i.e. include subentities)
NSError *err;
NSUInteger count = [moc countForFetchRequest:request error:&err];
if(count == NSNotFound) {
//Handle error
}
[request release];
To be clear, you aren't counting entities, but instances of a particular entity. (To literally count the entities, ask the managed object model for the count of its entities.)
To count all the instances of a given entity without fetching all the data, the use -countForFetchRequest:.
For example:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity: [NSEntityDescription entityForName: entityName inManagedObjectContext: context]];
NSError *error = nil;
NSUInteger count = [context countForFetchRequest: request error: &error];
[request release];
return count;
Swift
It is fairly easy to get a count of the total number of instances of an entity in Core Data:
let context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "MyEntity")
let count = context.countForFetchRequest(fetchRequest, error: nil)
I tested this in the simulator with a 400,000+ object count and the result was fairly fast (though not instantaneous).
I'll just add that to make it even more efficient... and because its just a count, you don't really need any property value and certainly like one of the code examples above you don't need sub-entities either.
So, the code should be like this:
int entityCount = 0;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"YourEntity" inManagedObjectContext:_managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setIncludesPropertyValues:NO];
[fetchRequest setIncludesSubentities:NO];
NSError *error = nil;
NSUInteger count = [_managedObjectContext countForFetchRequest: fetchRequest error: &error];
if(error == nil){
entityCount = count;
}
Hope it helps.
I believe the easiest and the most efficient way to count objects is to set NSFetchRequest result type to NSCountResultType and execute it with NSManagedObjectContext countForFetchRequest:error: method.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:entityName];
fetchRequest.resultType = NSCountResultType;
NSError *fetchError = nil;
NSUInteger itemsCount = [managedObjectContext countForFetchRequest:fetchRequest error:&fetchError];
if (itemsCount == NSNotFound) {
NSLog(#"Fetch error: %#", fetchError);
}
// use itemsCount
I wrote a simple utility method for Swift 3 to fetch the count of the objects.
static func fetchCountFor(entityName: String, predicate: NSPredicate, onMoc moc: NSManagedObjectContext) -> Int {
var count: Int = 0
moc.performAndWait {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: entityName)
fetchRequest.predicate = predicate
fetchRequest.resultType = NSFetchRequestResultType.countResultType
do {
count = try moc.count(for: fetchRequest)
} catch {
//Assert or handle exception gracefully
}
}
return count
}
It's really just this:
let kBoat = try? yourContainer.viewContext.count(for: NSFetchRequest(entityName: "Boat"))
"Boat" is just the name of the entity from your data model screen:
What is the global yourContainer?
To use core data, at some point in your app, one time only, you simply go
var yourContainer = NSPersistentContainer(name: "stuff")
where "stuff" is simply the name of the data model file.
You'd simply have a singleton for this,
import CoreData
public let core = Core.shared
public final class Core {
static let shared = Core()
var container: NSPersistentContainer!
private init() {
container = NSPersistentContainer(name: "stuff")
container.loadPersistentStores { storeDescription, error in
if let error = error { print("Error loading... \(error)") }
}
}
func saveContext() {
if container.viewContext.hasChanges {
do { try container.viewContext.save()
} catch { print("Error saving... \(error)") }
}
}
}
So from anywhere in the app
core.container
is your container,
So in practice to get the count of any entity, it's just
let k = try? core.container.viewContext.count(for: NSFetchRequest(entityName: "Boat"))
In Swift 3
static func getProductCount() -> Int {
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Product")
let count = try! moc.count(for: fetchRequest)
return count
}
If you want to find count for specific predicated fetch, i believe this is the best way:
NSError *err;
NSUInteger count = [context countForFetchRequest:fetch error:&err];
if(count > 0) {
NSLog(#"EXIST");
} else {
NSLog(#"NOT exist");
}
Swift 5 solution:
var viewContext: NSManagedObjectContext!
do {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "insertName")
let count = try viewContext.count(for: fetchRequest)
print("Counted \(count) objects")
}
catch {
print("Error")
}