NSCoding NSKeyedUnarchiver unarchiveObjectWithFile: returning null - ios

I am trying to save some values from my app using NSCoding. I'm able to save the value but not able to retrieve it.
Here's where I am declaring the protocol:
#interface AddReminderEventViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource, NSCoding>
Here's where I'm complying with the protocol:
-(void)encodeWithCoder:(NSCoder *)enCoder
{
[enCoder encodeObject:self.eventRepeatDurationDate forKey:kEventRepeatDuration];
[enCoder encodeObject:self.eventIDsMutableArray forKey:kEventIDsMutableArray];
[enCoder encodeObject:self.eventRepeatDurationString forKey:#"mytest"];}
and here:
-(id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]){
self.eventRepeatDurationDate = [[decoder decodeObjectForKey:kEventRepeatDuration] retain];
self.eventIDsMutableArray = [[decoder decodeObjectForKey:kEventIDsMutableArray] retain];
self.eventRepeatDurationString = [[decoder decodeObjectForKey:#"mytest"] retain];} return self; }
and here's where I call the methods to do the archiving and unarchiving:
[self saveDataToDisk];
[self loadDataFromDisk];
and here are the bodies of these methods and it's NSLog contents:
- (void)saveDataToDisk {
NSString *reminderEventIDsPathString = #"~/Library/Application Support/ReminderIDs.archive";
//reminderEventIDsPathString = #"~/Library/Application Support/ReminderIDs.archive";
reminderEventIDsPathString = [reminderEventIDsPathString stringByExpandingTildeInPath];
NSLog(#"WATCH1: reminderEventIDsPathString is %#", reminderEventIDsPathString);
NSMutableDictionary *rootObject;
rootObject = [NSMutableDictionary dictionary];
[rootObject setValue:eventRepeatDurationString forKey:#"mytest"];
NSLog(#"1rootObject IS %#", rootObject);
[NSKeyedArchiver archiveRootObject:rootObject toFile:reminderEventIDsPathString];}
reminderEventIDsPathString is /Users/tester/Library/Application Support/iPhone Simulator/5.0/Applications/E26D57DE-C4E1-4318-AEDD-7207F41010A9/Library/Application Support/ReminderIDs.archive
2012-01-16 15:47:48.578 [29658:15503] 1rootObject IS {mytest = 7;}
and here is the unarchiver code along with its NSLog contents:
- (void)loadDataFromDisk {
NSString *testValue = [[NSString alloc] init];
NSString *reminderEventIDsPathString = #"~/Library/Application Support/ReminderIDs.archive";
reminderEventIDsPathString = [reminderEventIDsPathString stringByExpandingTildeInPath];
NSLog(#"WATCH2: reminderEventIDsPathString is %#", reminderEventIDsPathString);
NSMutableDictionary *rootObject;
rootObject = [[NSKeyedUnarchiver unarchiveObjectWithFile:reminderEventIDsPathString] retain];
NSLog(#"2rootObject IS %#", rootObject);
NSLog(#"WATCH3 - %#", [rootObject objectForKey:#"mytest" ]);
if ([rootObject valueForKey:#"mytest"]) {
testValue = [rootObject valueForKey:#"mytest"];
NSLog(#"WATCH: testValue is %#", testValue); } }
2012-01-16 15:48:14.965 [29658:15503] WATCH2: reminderEventIDsPathString is /Users/tester/Library/Application Support/iPhone Simulator/5.0/Applications/E26D57DE-C4E1-4318-AEDD-7207F41010A9/Library/Application Support/ReminderIDs.archive
2012-01-16 15:48:17.879 [29658:15503] 2rootObject IS (null)
What am I missing that I'm not able to unarchive the contents? I'm just focusing on the easiest of the values in my encoder/decoder methods just to test it but I'm not even able to get the string value to work.
Thanks

The path where you save and load your reminder is wrong. Maybe replace to this
NSString *reminderEventIDsPathString = [[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingPathComponent:#"ReminderIDs.archive"];

Related

Create a Model Object from JSON/Dictionary without Objective-c Runtime & hard coding or keys

I am trying to parse a JSON and create a Model(a nested class structure). I can do it using JSONModel (available on Github) but I do not want to use Obj-c Runtime.
Is there any other way to achieve this? Lets see what I did with some dummy code.
NSDictionary *dictionary = #{#"personalDetail":#{#"name":#"Anoop kumar vaidya",
#"dob":#"3 Aug 1981",
#"sex":#"Male"},
#"workDetail":#{#"current":#"Photon",
#"y2014":#"TechM",
#"y2012":#"Exilant",
#"y2010":#"RSG"},
#"educationDetail":#{#"pg":#"Mca - 63%",
#"ug":#"Bca -63%",
#"s12":#"58%",
#"s10":#"66%"
}
};
self.model = [[MainModel alloc] initWithMainModel:dictionary];
Implementation of MainModel, here you see the keys are hard coded. I want to avoid it, to make it generic.
-(id)initWithMainModel:(NSDictionary *)dict{
for (NSString *key in dict) {
[self putValue:dict[key] forKey:key];
}
return self;
}
-(void)putValue:(NSDictionary *)value forKey:(NSString *)key{
if ([key isEqualToString:#"personalDetail"]) {
self.personalDetail = [[Personal alloc] initWithDictionary:value];
}
else if ([key isEqualToString:#"workDetail"]){
self.workDetail = [[Work alloc] initWithDictionary:value];
}
else if ([key isEqualToString:#"educationDetail"]){
self.educationDetail = [[Education alloc] initWithDictionary:value];
}
}
Implementation of Personal. Similarly other two classes, Work & Education are implemented.
-(id)initWithDictionary:(NSDictionary *)dict{
for (NSString *key in dict) {
[self putValue:dict[key] forKey:key];
}
return self;
}
-(void)putValue:(NSString *)value forKey:(NSString *)key{
[self setValue:value forKey:key];
}
You can use the key and extract the class name of it. Then you get the class for this identifier witch NSClassFromString() to get the class and instantiate it with +alloc as usual:
key = …;
if ([key hasSuffix:#"Detail"])
{
NSString *className = [key substringToIndex:[key length]-[#"Detail length]];
className = [[[className substringToIndex:1] uppercaseString] stringByAppendingString:[className substringFromIndex:1]];
}
id instance = [[NSClassFromString( className ) alloc] initWithDictionary:…];
I said: You can do it this way. I did not say: You should.

NSCoding - saving array to file or nsdefaults

Afternoon all,
Working on my first iphone app.
I am trying to save an array of an array either to file or nsuserdefaults.
Data is like this...
MainArray (contains 3 below arrays)
Array1 (contains 3 strings)
Array2 (contains 3 strings)
Array3 (contains 3 strings)
So far I've been reading about saving things to nsuserdefaults, and saving to file. Not sure which is the right way or benefits of either but I start trying saving to file.
below is my custom object to save information.
#implementation UserSettingsClass
+ (instancetype)sharedUserData{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self loadInstance];
//sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
-(void)encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject:self.arrayUserSettings forKey:#"someArray"];
[encoder encodeObject:self.userDescription forKey:#"testDesc"];
}
- (id)initWithCoder:(NSCoder *)decoder{
if ((self = [super init])){
self.userDescription = [decoder decodeObjectForKey:#"testDesc"];
self.arrayUserSettings = [decoder decodeObjectForKey:#"someArray"];
}
return self;
}
+(NSString *)filePath{
static NSString *filePath = nil;
if (!filePath){
filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:#"gamedata"];
}
return filePath;
}
+(instancetype)loadInstance{
NSData *decodedData = [NSData dataWithContentsOfFile:[UserSettingsClass filePath]];
if (decodedData){
UserSettingsClass *gameData = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData];
return gameData;
}
return [[UserSettingsClass alloc] init];
}
-(void)save{
NSData *encodedData = [NSKeyedArchiver archivedDataWithRootObject:self];
[encodedData writeToFile:[UserSettingsClass filePath] atomically:YES];
}
and here is the main class where I am trying to use it.
//initialize variables
//_userArray = [[NSMutableArray alloc] init];
//_userDescription = [[NSString stringWithFormat:#"testDescription"] init];
//_userLoginID = [[NSString stringWithFormat:#"testLogin"] init];
//_userPW = [[NSString stringWithFormat:#"testPassword"] init];
// [_userArray addObject:[UserSettingsClass sharedUserData].userDescription];
//[_userArray addObject:_userLoginID];
//[[UserSettingsClass sharedUserData].arrayUserSettings addObject: [UserSettingsClass sharedUserData].userDescription];
NSMutableArray *tempArray = [UserSettingsClass sharedUserData].arrayUserSettings;
//[_userArray addObject:_userPW];
//save data to shared singleton class
//[[UserSettingsClass sharedUserData].arrayUserSettings addObject:_userArray];
//NSMutableArray *tempArray = [[NSMutableArray alloc] init];
//tempArray = [UserSettingsClass sharedUserData].arrayUserSettings;
//[UserSettingsClass sharedUserData].highScore = 10;
//int i = [UserSettingsClass sharedUserData].highScore;
//[UserSettingsClass sharedUserData].userDescription = #"hello";
NSString *temp2 = [UserSettingsClass sharedUserData].userDescription;
I am able to save the single string, but I must be doing something wrong.
The single string I saved was just to see if I can get it working. My goal is to save the main array to file (or nsuserdefaults), which contain about 3 objects (array)... and each of those arrays contains 3 strings each.
any I doing something blatantly wrong?
You are trying to hard.
If what you want to save is just NSArrays and NSStrings to you do not need so add an NSCoding, these types already conform to NSCoding. Just Archive to a file or "shudder" save to NSUserDefaults "/shudder".
It is really better to create a Data Model class and use NSArchiver to save and restore from a file in the Documents directory.

Set UILabel's text from custom method

I created a method which gets data from a server, everything works fine except when I try to set string to for example UILabel or UITextView, nothing shows and changed ! here is my code :
- (void)viewDidLoad
{
[super viewDidLoad];
[self getDataFromURL:#"http://somesites.net/panel/services?action=events&num=1"
setTitle:_eTitle1.text image:_eImage1 description:_eNews1.text];
}
Getting Data :
-(void)getDataFromURL:(NSString*)url setTitle:(NSString*)eTitle
image:(UIImageView*)eImages description:(NSString*)eDescriptions {
NSURL *URL = [NSURL URLWithString:url];
NSError *error1;
NSString *strPageContent = [NSString stringWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:&error1];
strPageContent = [strPageContent gtm_stringByUnescapingFromHTML];
if ([strPageContent rangeOfString:#"<plist version=\"1.0\">"].location != NSNotFound) {
NSRange range = [strPageContent rangeOfString:#"<plist version=\"1.0\">"];
strPageContent = [strPageContent substringWithRange:NSMakeRange(range.location+range.length, strPageContent.length-(range.location+range.length))];
strPageContent = [strPageContent stringByReplacingOccurrencesOfString:#"</plist>" withString:#""];
}
NSError *error = nil;
NSDictionary *dict = [XMLReader dictionaryForXMLString:strPageContent options:XMLReaderOptionsProcessNamespaces
error:&error];
if ([dict count]>0) {
NSDictionary *dictInner = [dict objectForKey:#"dict"];
NSArray *arrValues = [dictInner objectForKey:#"string"];
NSString * strTitle = [[arrValues objectAtIndex:0] objectForKey:#"text"];
NSString *strImage = [[arrValues objectAtIndex:1] objectForKey:#"text"];
NSString * strDescription = [[arrValues objectAtIndex:2] objectForKey:#"text"];
eTitle = strTitle;
eDescriptions = strDescription;
// [eImages setImageWithURL:[NSURL URLWithString:strImage]
// placeholderImage:[UIImage imageNamed:#"loadingPad.jpg"]];
NSLog(#"Title: %# | Image: %# | Desc: %#",eTitle,strImage,eDescriptions);
}
}
compiler gives me the right information ! but these string could not set to my lable , IF I put my lable's string into the method it works !!! :
_eTitle1.text = strTitle ;
It's completely normal: when you pass the "text" object to the method, you are passing the pointer to it. Assigning to it directly another NSString object will just assign a new pointer. In order to have side effect on a string you gotta use NSMutableString, but the UILabel has just an immutable NSString for the text attribute. So the only solution is to pass the UILabel or pass an initialized empty mutable string inside the method, change the content via [eTitleText setString:strTitle] and then, outside the method, assign it to the UILabel text attribute.
So, either you change the method like this (as you already did):
-(void)getDataFromURL:(NSString*)url setTitle:(UILabel*)eTitle
image:(UIImageView*)eImages description:(NSString*)eDescriptions {
...
eTitle.text = strTitle;
...
and using it like this:
- (void)viewDidLoad
{
[super viewDidLoad];
[self getDataFromURL:#"http://somesites.net/panel/services?action=events&num=1"
setTitle:_eTitle1 image:_eImage1 description:_eNews1.text];
}
Or you can go this other way:
-(void)getDataFromURL:(NSString*)url setTitle:(NSMutableString*)eTitle
image:(UIImageView*)eImages description:(NSString*)eDescriptions
...
[eTitle setString:strTitle];
...
and using it like this:
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableString *titleText = [NSMutableString new];
[self getDataFromURL:#"http://somesites.net/panel/services?action=events&num=1"
setTitle:titleText image:_eImage1 description:_eNews1.text];
eTitle1.text = titleText;
}

Objective C memory management - "pointer being freed was not allocated" errors

I'm trying to learn objective-c (I'm very new to that) and I have issues with memory management...
I'm developing an iPad app that uses TouchXML.
I've created my class that extends CXMLDocument and does some initialisation by reading some contents and saving into properties.
Here is my code (SimpleManifest.h):
#interface SimpleManifest : CXMLDocument {
CXMLNode *_defaultOrganization;
NSString *_title;
NSDictionary *dictionary;
}
#property (readonly) CXMLNode *defaultOrganization;
#property (readonly) NSString* title;
- (id) initWithPath:(NSString *)path options:(NSUInteger)options error:(NSError **)error;
#end
(SimpleManifest.m):
#import "SimpleManifest.h"
#import "CXMLNode_XPathExtensions.h"
#implementation SimpleManifest
- (id) initWithPath:(NSString *)path options:(NSUInteger)options error:(NSError **)error
{
/*
NSURL *theURL = [[[NSURL alloc] initFileURLWithPath:path] autorelease];
self = [self initWithContentsOfURL:theURL options:options error:error];
*/
NSData *data = [NSData dataWithContentsOfFile:path];
NSString *s = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
self = [self initWithXMLString:s options:options error:error];
if (self==nil) return nil;
// load main props
dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
#"http://www.imsglobal.org/xsd/imscp_v1p1", #"imscp",
#"http://ltsc.ieee.org/xsd/LOM", #"lom", nil];
// defualt organization
#try {
CXMLNode *orgsElem = [[[self childAtIndex:0] nodesForXPath:#"//imscp:organizations" namespaceMappings:dictionary error:nil] objectAtIndex:0];
NSString *xpath = [NSString stringWithFormat:#"//imscp:organization[#identifier='%#']", [[orgsElem attributeForName:#"default"] stringValue]];
_defaultOrganization = [[[self childAtIndex:0] nodesForXPath:xpath namespaceMappings:dictionary error:nil] objectAtIndex:0];
/*
NSArray *nodes = [[self childAtIndex:0] nodesForXPath:#"//imscp:organizations" namespaceMappings:dictionary error:nil];
NSString *xpath = [NSString stringWithFormat:#"//imscp:organization[#identifier='%#']", [[[nodes objectAtIndex:0] attributeForName:#"default"] stringValue]];
_defaultOrganization = [[[self childAtIndex:0] nodesForXPath:xpath namespaceMappings:dictionary error:nil] objectAtIndex:0];
*/
CXMLNode *titleElem = [[[self childAtIndex:0]
nodesForXPath:#"//lom:general/lom:title/lom:string"
namespaceMappings:dictionary
error:nil] objectAtIndex:0];
_title = [[titleElem stringValue] copy];
} #catch (NSException * e){
self = nil;
return nil;
}
return self;
}
#end
Later on in another class I do:
- (BOOL) isValidSCORMLesson:(NSString*) path {
NSString *manifPath = [path stringByAppendingPathComponent:#"imsmanifest.xml"];
if (![[NSFileManager defaultManager] fileExistsAtPath: manifPath isDirectory: NO])
return NO;
SimpleManifest *manifest = [[[SimpleManifest alloc] initWithPath:manifPath options:0 error:nil] autorelease];
NSLog(#"%#", manifest.defaultOrganization);
NSLog(#"%#", manifest.title);
return (manifest!=nil);
}
It gives me tons of "pointer being freed was not allocated" errors...
The thing changes if I comment out the NSLog calls above or just log the manifest.title property.
Project is not using ARC, so I'm sure I'm doing something wrong with memory management.
Can someone please help me understand where I'm doing wrong? Thanks!
There isn't anything obviously wrong with that code that would cause malloc errors. Best guess is that there is a bug in the CXMLDocument class/library or some mistake in the way you are using it.
Note that a "pointer being freed was not allocated" means that someone called free() (or dealloc, effectively) on a pointer to a piece of memory that was not allocated in the first place. It usually gives you a breakpoint you can set that will then give you a backtrace of exactly where it happened.
Some comments:
(1) Do not #try/#catch in that fashion. Just don't catch at all. The pattern you are using will hide any errors. Exceptions are not meant to be recoverable in iOS/Cocoa.
(2) You can create an NSString instance directly from a file; no need to load via NSData first.
(3) You should use ARC.

Memory Leak in NSMutableDictionary initWithContentsOfFile

This is my code: (customNames and customNamesArray are static variables)
-(void) loadCustomDataFromDisk
{
NSString *fullPath = [self filePathAndFileName: #"customData.plist"];
if ( ![[NSFileManager defaultManager] fileExistsAtPath: fullPath] )
{
NSLog(#"Loading file fails: File not exist");
customNames = [[NSMutableDictionary alloc] init];
customNamesArray = [[NSMutableArray alloc] init];
}
else
{
NSMutableDictionary *customItems = [[NSMutableDictionary alloc] initWithContentsOfFile: fullPath];
customNames = [customItems objectForKey: #"customNamesDict"];
customNamesArray = [customItems objectForKey: #"customNamesArray"];
if (!customItems)
NSLog(#"Error loading file");
[customItems release];
}
}
-(void) saveCustomDataToDisk
{
NSString *path = [self filePathAndFileName: #"customData.plist"];
NSMutableDictionary *customItems = [[NSMutableDictionary alloc] init];
[customItems setObject: customNames forKey: #"customNamesDict"];
[customItems setObject: customNamesArray forKey: #"customNamesArray"];
BOOL success;
success = [customItems writeToFile:path atomically:YES];
if (!success)
NSLog(#"Error writing file: customDataDict.plist");
[customItems release];
}
According to Build and Analyze, I have a potential leak in loading customItems
NSMutableDictionary *customItems = [[NSMutableDictionary alloc] initWithContentsOfFile: fullPath];
true enough, according to Instruments, I do have a leak in that part. But when I tried release or autoreleasing customItems, my app crashes. Even if I change NSMutableDictionary to NSDictionary, I still have the leak.
How do I fix this?
Any help would be very much appreciated. :) Thanks :)
You have to retain customNames and customNamesArray because you are using reference from dictionary customItems and after passing reference you are releasing it.
customNames = [[customItems objectForKey: #"customNamesDict"] retain];
customNamesArray = [[customItems objectForKey: #"customNamesArray"] retain];
Now you can release customItems.
Your code is right as I can see. You can see answer here and may be it helps - Leak problem with initWithContentsOfFile
I have only one question: You create NSString *fullPath and never release it. Is it autoreleased string? If so - your code is fine.

Resources