Lifetime of NSString created within a case statement - ios

I have an iOS program using Core Data backing onto DynamoDB using ARC.
In it I have a data model with the following class declared:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface Track : NSManagedObject
#property (nonatomic, retain) NSString * trackId;
#end
-------------------------------------------------
#import "Track.h"
#implementation Track
#dynamic trackId;
#end
I then have a class creating an instance of the ManagedObject and persisting it like so:
NSString *trackId = #"ABC123";
Track *track = (Track*)[NSEntityDescription insertNewObjectForEntityForName:#"Track" inManagedObjectContext:context];
track.trackId=trackId;
NSError *error;
if (![appDelegate.managedObjectContext save:&error])
{
NSLog(#"error: %#", error);
}
This works as expected. However, if I implement the same using a switch to alter the value of trackId as below:
for (int i=0; i<1; i++) {
NSString *trackId;
switch (i) {
case 0:
trackId = #"XYZ789";
break;
}
Track *track = (Track*)[NSEntityDescription insertNewObjectForEntityForName:#"Track" inManagedObjectContext:context];
track.trackId=trackId;
}
NSError *error;
if (![appDelegate.managedObjectContext save:&error])
{
NSLog(#"error: %#", error);
}
then I get the following exception:
2013-01-16 14:44:00.013 DjTest2[2351:c07] XYZ789
2013-01-16 14:44:00.014 DjTest2[2351:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** setObjectForKey: key cannot be nil'
*** First throw call stack:
(0x183e012 0x1663e7e 0x18c10de 0x107c9bf 0x39799 0x1ff5e4 0xc7831 0xf533c 0x3411 0x68c817 0x68c882 0x5dba25 0x5dbdbf 0x5dbf55 0x5e4f67 0x5a8fcc 0x5a9fab 0x5bb315 0x5bc24b 0x5adcf8 0x268bdf9 0x268bad0 0x17b3bf5 0x17b3962 0x17e4bb6 0x17e3f44 0x17e3e1b 0x5a97da 0x5ab65c 0x258d 0x24b5)
libc++abi.dylib: terminate called throwing an exception
I assume there is some issue I'm not aware of regarding variable scope / retention using ARC where the NSString literal is created within the scope of a switch, but I can't find any reference to it.
Does anyone know what the root cause to this is and how you would efficiently deal with persisting a large number of objects without being able to perform such manipulation?
Note: I have mentioned backing on to DynamoDB for completeness, but this exception is raised before any attempt to call to AWS is made.

I'm afraid I have lead us astray with my code example above which I updated for clarity when entering on SO.
The actual problem is not that it is within the switch statement, rather that some of the real keys I'm using have spaces in them which I assume is then being restricted by Core Data given that DynamoDB has no such restriction.

Related

Adding CoreData into an existing project using Objective-C

I'm trying to create a data storage for my application using CoreData. From what I know, Xcode 8 CoreData is using persistentContainer instead of managedObjectContext.
I've created a data model with my required entities and created an NSManagedObject subclass from the Editor menu.
My problem is that when I want to use the persistentContainer, there is no identifier found.
#import "UserCredentials+CoreDataClass.h"
//Fetch all username to array
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]initWithEntityName:#"UserCredentials"];
NSError *requestError = nil;
//I couldn't find the persistent container even though I had imported my header file.
NSArray *usernames = [self.persistentContainer.viewContext executeFetchRequest:fetchRequest error:&requestError];
I realised that my CoreDataClass did not even have the property persistentContainer at all. Where can I declare this at, so I can access my data storage?
I am assuming you have selected core data option while creating your object. Your object context is null because it is store into AppDelegate. So you need to get context reference from appdelegate like below.
NSManagedObjectContext *context = ((AppDelegate*)[[UIApplication sharedApplication] delegate]).persistentContainer.viewContext;
NSArray *usernames = [context executeFetchRequest:fetchRequest error:&requestError];
You should create property
//.h file
#property (readonly, strong) NSPersistentContainer *persistentContainer;
//.m file
#synthesize persistentContainer = _persistentContainer;
- (NSPersistentContainer *)persistentContainer {
// The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
#synchronized (self) {
if (_persistentContainer == nil) {
_persistentContainer = [[NSPersistentContainer alloc] initWithName:#"CoreDataModel"]; //e.g. CoreDataModel.xcdatamodeld
[_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
if (error != nil) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
RLog(#"Unresolved error %#, %#", error, error.userInfo);
abort();
}
}];
}
}
return _persistentContainer;
}

NSUserDefaults setObject:nil vs. setNilValueForKey

My app crashes whenever I try the following line:
[[NSUserDefaults standardUserDefaults] setNilValueForKey:#"my_key"];
with this error: 'NSInvalidArgumentException', reason: '[<NSUserDefaults 0x7a2423d0> setNilValueForKey]: could not set nil as the value for the key my_key.'
But when I do this it seems to work:
[[NSUserDefaults standardUserDefaults] setObject:nil forKey:#"my_key"];
Can someone explain the difference between these 2 functions?
setNilValueForKey: is part of the NSKeyValueCoding protocol and is not intended to be called directly, just overridden by classes with custom NSKeyValueCoding implementations. setObject:forKey:, however, is a method provided by NSUserDefaults and essentially removes the key from the defaults when sent with a nil object (though this behavior may look different in Swift).
According to Apple doc : "The default implementation raises an NSInvalidArgumentException." Ovbiusly when you call it the NSInvalidArgumentException will be launched.
You can call, but you will are launching an exception. Where use Apple this methods, well according to its doc. again: "Invoked by setValue:forKey: when it’s given a nil value for a scalar value (such as an int or float)."
Let's see a little example:
We create a new class, TestObject, we only add a couple of properties in its header (.h) file:
#import <Foundation/Foundation.h>
#interface TestObject : NSObject
#property (nonatomic, strong) NSString *keyString;
#property (nonatomic) int keyInteger;
#end
Well, now we import it in our viewController and we add the next code to the viewDidLoad methods in order to test:
TestObject *testObject = [[TestObject alloc] init];
[testObject setValue:#"Hola" forKey:#"keyString"];
[testObject setValue:#2 forKey:#"keyInteger"];
#try {
// This is the method wich launch the exception.
[testObject setValue:nil forKey:#"keyInteger"];
// If you test this the exception won't be launched.
// [testObject setKeyInteger:nil];
}
#catch (NSException *exception) {
NSLog(#"The exception name is %#",[exception name]);
}
NSLog(#"View values: %#\n%d",testObject.keyString,testObject.keyInteger);
Of course we can override this methods in our subclass, and change its behavior.
All info: https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Protocols/NSKeyValueCoding_Protocol/#//apple_ref/occ/instm/NSObject/setNilValueForKey:

CoreData + restkit + creating new entities

I am using RestKit with Coredata and fetching data from the server and displaying.
Now I am doing a post from the client and this object gets updated as part of the response that comes back from the server. This is where the problems starts.
I looked for the correct way to implement this and came across 2 main points.
MOCs should not be shared across threads
An object created in the MOC is not available in another thread without saving.
But i think since the record gets updated from server response, its no longer finding the orig object. I just dont knw what the right fix is.
Here is my code
1. Create local entity
NSEntityDescription *itemEntity = [NSEntityDescription entityForName:ENTITY_ITEM inManagedObjectContext:self.managedObjectContext];
Item *item = [[Item alloc]initWithEntity:itemEntity insertIntoManagedObjectContext:self.managedObjectContext];
// Set params on item here
// Then save it
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
DBGLog(#"Tried to save the new item but failed - error %#, %#", error, [error userInfo]);
}
// Then I make the RestKit call to post the item
// The server updates the item Id
[SharedManager postItem:item success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
// successful case
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
// failure case
}];
It looks like when its trying to make the response it doesnt find the object.
And i get this exception -
*** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x156c87b0 <x-coredata://A42ABF18-01B6-4D78-B81B-62D8B604EB52/Item/p6>''
*** First throw call stack:
(0x2f853f0b 0x39feace7 0x2f5b7fd1 0x2f61a655 0x2f6246a7 0x2f6326e5 0x2f632a95 0x2f63356f 0x3a4d3d3f 0x3a4d86c3 0x2f628e7b 0x2f633271 0x2f5c7f49 0x1c67fb 0x2f62b9cd 0x3a4d9b3b 0x3a4d3d3f 0x3a4d66c3 0x2f81e681 0x2f81cf4d 0x2f787769 0x2f78754b 0x346f46d3 0x320e6891 0x72561 0x3a4e8ab7)
libc++abi.dylib: terminating with uncaught exception of type _NSCoreDataException
If I dont do a "save" then I see Cocoa Error 133000 on 4S devices. So there is definitely something I am messing up.
Appreciate any insights!
Your comment is along the correct lines, but not the correct solution. The problem is that you only save the main thread context and the change doesn't get pushed up to the parent (the persistent context). So, instead of calling if (![self.managedObjectContext save:&error]) { you should be calling if (![self.managedObjectContext saveToPersistentStore:&error]) {

Unrecognized selector when deleting a NSManagedObject

I'd like to delete a core data object by fetched the object first, so
in FetchObject.m
- (void) actionDelete {
AModel *aModel = [[aModel alloc] init];
AObj *aObj = [aModel readDataWithAttributeName:#"keyword" attributeValue:#"value"];
[aModel deleteObject:aObj];
}
aObj did fetch and obtain.
in AModel.m
- (void)deleteObject:(AObj *)aObj
{
[appDelegate.managedObjectContext delete:aObj];
NSError *error;
if (![appDelegate.managedObjectContext save:&error]) {
NSLog(#"Error: %#", [error description]);
}
}
But, when I test it, here came out an error
-[NSManagedObjectContext delete:]: unrecognized selector sent to instance 0xa43ece0
After searching the solution a bit, seems like the target has been release before deleteObject.
Is there any way to solve the problem?
The following code is causing the issue:
[appDelegate.managedObjectContext delete:aObj];
Replace it with:
[appDelegate.managedObjectContext deleteObject:aObj];
NSManagedObjectContext doesn't have a delete method, it only has a deleteObject method.
- (void)deleteObject:(NSManagedObject *)object
Parameters
object
A managed object.
Discussion
When changes are committed, object will be removed from the uniquing
tables. If object has not yet been saved to a persistent store, it is
simply removed from the receiver.

NSKeyedArchiver deallocated without having had -finishEncoding called on it

In core data I've setup a simple entity called GolferEntity that contains simply a golferObject (transformable type) and playerId (string).
- (void)addOrUpdateGolfer:(GolferObject *)feedObj
{
NSLog(#"In add or update Feed");
// get reference to local (stored) golfer item, create it if needed
Golfer *localGolfer = [self golferForId:[feedObj PlayerId]];
if (localGolfer == nil) {
localGolfer = (Golfer *)[NSEntityDescription insertNewObjectForEntityForName:#"GolferEntity" inManagedObjectContext:[self managedObjectContext]];
[localGolfer setPlayerId:[feedObj PlayerId]];
}
// set folder fields
[localGolfer setGolferObj:feedObj];
// apply update
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"%#", [error localizedDescription]);
// return nil;
}
NSLog(#"successfully saved user: %#", [feedObj PlayerId]);
//return localFolder;
}
The code is giving me a runtime error which reads:
* -[NSKeyedArchiver dealloc]: warning: NSKeyedArchiver deallocated without having had -finishEncoding called on it.
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[GolferObject encodeWithCoder:]: unrecognized selector sent to instance 0x4b7520'
-- No where in my code have i alloc'd NSKeyedArchiver so I'm assuming this is something that done by core data? also my GolferObject does not have an encodeWithCoder method? I do not know where this is coming from?
I needed to encode before submitting to my custom object in CoreData. I needed to use the - (void)encodeWithCoder:(NSCoder *)encoder; and - (id)initWithCoder:(NSCoder *)decoder; methods.

Resources