NSDictionary memory leak after tring to overwrite content - ipad

Trying to track down a memory leak. I have it traced to a synthesized NSDictionary that is used to store plist data read in from the documents folder on startup. The first time it runs, no leaks. But later in the app I make a web call that overwrites the NSDictonary object and thats where I see the leak.
The update works well, but in instruments, I am see a leak at the point of overwriting the NSDictionary.
I've tried the standard release and nil before setting the synthesized NSDictionary to the new content. Not sure what to try next.
Usually find the answer I am looking for right away here. But this ones stumping me.
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
Hubplist = (NSDictionary *)[NSPropertyListSerialization
propertyListWithData:plistXML
options:NSPropertyListImmutable
format:&format
error:NULL];

if you have retained or allocated Hubplist anywhere you need to release it before overriding it.
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
[Hubplist release];
Hubplist = nil;
Hubplist = (NSDictionary *)[NSPropertyListSerialization
propertyListWithData:plistXML
options:NSPropertyListImmutable
format:&format
error:NULL];
// Also make sure to retain it if needed
[Hubplist retain];

Related

How to update the existing .plist?

How to update the existing plist with info? I have simple dictionary with strings. Here is my save method:
#define DOCUMENTS [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]
- (IBAction)saveData:(id)sender {
NSMutableDictionary *data = [NSMutableDictionarydictionaryWithObjects:#[self.textFieldOne.text, self.textFieldTwo.text, self.textFieldThree.text] forKeys:#[#"Key1", #"Key2", #"Key3"]];
NSError *error = nil;
NSData *dataToPlist = [NSPropertyListSerialization dataWithPropertyList:data format:NSPropertyListXMLFormat_v1_0 options:0 error:&error];
NSString *path = [DOCUMENTS stringByAppendingPathComponent:#"userData.plist"];
[dataToPlist writeToFile:path atomically:YES];
}
So, it's OK when I call this method from Class 1, but when I want to update this plist with other info from Class 2 – it overwrites old info with new info. What I should do to update the existing plist with new info from other classes?
You need to load the existing plist into an NSDictionary or NSArray (as appropriate), then update the data with the additional data, and finally write out the updated data to the plist file.
Basically the existing one you have to read into dictionary using dictionarywithcontentsfile method and then on the basis of your requirement use setobjecwt method into dictionary and then write to plist. So that old value will be retain there and you can updates new as well.

write to plist - error [duplicate]

I have created save.plist in a resource folder. I have written some data within that directly (without using coding). I am able to read that data but I'm not able to write through code to the same save.plist. By using following code I am trying to write the data but it gets stored within my .app plist.
The code is here
NSString *errorDesc = nil;
NSPropertyListFormat format;
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"save" ofType:#"plist"];
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSMutableDictionary *temp = (NSMutableDictionary *)[NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format errorDescription:&errorDesc];
if (!temp) {
NSLog(errorDesc);
[errorDesc release];
}
// [temp setValue:#"123" forKey:#"line1"];
// [temp writeToFile:plistPath atomically: YES];
//Reading data from save.plist
NSLog([temp objectForKey:#"name"]);
NSLog([temp objectForKey:#"wish"]);
NSNumber *num=[temp valueForKey:#"roll"];
int i=[num intValue];
printf("%d",i);
//writitng the data in save.plist
[temp setValue:#"green" forKey:#"color"];
[temp writeToFile:plistPath atomically: NO];
NSMutableDictionary *temp1 = (NSMutableDictionary *)[NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format errorDescription:&errorDesc];
NSLog([temp objectForKey:#"color"]);
I want that, the data which I want to write should get written into save.plist only which is stored in references. I am new with this concept. So if anyone knows it please help me.
Thanks in advance.
:-)
I don't know if I understand your question, but if you want to write into a .plist within your .app bundle you are probably doing something wrong. If you want to store preferences, you should consider using NSUserDefaults.
If you really want to modify a bundled .plist - here is some code:
NSString *plistPath = nil;
NSFileManager *manager = [NSFileManager defaultManager];
if (plistPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"Contents/Info.plist"])
{
if ([manager isWritableFileAtPath:plistPath])
{
NSMutableDictionary *infoDict = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
[infoDict setObject:[NSNumber numberWithBool:hidden] forKey:#"LSUIElement"];
[infoDict writeToFile:plistPath atomically:NO];
[manager changeFileAttributes:[NSDictionary dictionaryWithObject:[NSDate date] forKey:NSFileModificationDate] atPath: [[NSBundle mainBundle] bundlePath]];
}
}
Update:
Nate Flink pointed out that some of the NSFileManager methods used above are deprecated.
He posted an answer with the replacement methods below:
https://stackoverflow.com/a/12428472/100848
Updated version of the original awesome example by weichsel (thank you!). Xcode threw a couple warnings one of which is a deprecated method on NSFileManager. Updated here with non-deprecated methods from iOS 5.1
NSString *plistPath = nil;
NSFileManager *manager = [NSFileManager defaultManager];
if ((plistPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"mySpecial/PathTo.plist"]))
{
if ([manager isWritableFileAtPath:plistPath])
{
NSMutableDictionary *infoDict = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
[infoDict setObject:#"foo object" forKey:#"fookey"];
[infoDict writeToFile:plistPath atomically:NO];
[manager setAttributes:[NSDictionary dictionaryWithObject:[NSDate date] forKey:NSFileModificationDate] ofItemAtPath:[[NSBundle mainBundle] bundlePath] error:nil];
}
}
When you build the app, it will create an executable file "appName.app" and all the files are built in the bundle. Therefore, you can't access to resource folder when the app is running because all the data is in the bundle(not in folder).
However, you can access to a temp folder which contains some information of the app.
You can find the temp folder here:
Open finder--click on your username(under PLACES)--Library--Application Support--iPhone Simulator--User--Applications--(here you can find all the temp folders of your iPhone apps)
You can access to this temp folder by:
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
If you name your file save.plist, you can access to it like this:
NSString *filePath = [documentsDirectory stringByAppendingString:#"_save.plist"];
Then you just save your file to this filePath and it will appear in the temp folder named "Documents_save.plist".
*Note that the temp folder's name varies every time you run the app.
Recommend a book for you: 《Beginning iPhone Development--Exploring the iPhone SDK》. In Chapter 11 you can find what you want.
To summarize some of the other answers:
You're problem is that you're trying to write the file back into the folder that contains your application. That folder is not writable at runtime. Everything you're doing is fine, you just need to pick a different location to write your file to.
You can use the NSSearchPathForDirectoriesInDomains function to find a more suitable folder for this data. (Such as the #"Documents" folder.)
try this:
-(void)add:(NSRunningApplication *) app {
if ([self contains:app]) return;
[self.apps addObject:app.localizedName];
[self.apps writeToFile:self.dataFile atomically:YES];
}
from "Cocoa Programming".
you have to copy your plist into document directory...
because you cannot save anything without saving into document file....when you copied it will allow to write/modify on plist

How to create NSMutableArray by plist file? [duplicate]

This question already has answers here:
getting data from plist to NSMutableArray and writing the NSMutableArray to same plist iPhone
(3 answers)
Closed 9 years ago.
Previously, I used the following code to create an array for me and it worked.
bundle = [NSBundle mainBundle];
path = [bundle pathForResource:#"MultiSetting" ofType:#"plist"];
settingArray = [[NSMutableArray alloc] initWithContentsOfFile:path];
But after that, I wanted to modify the plist file, therefore, I used the following code to do that and it's NOT working.
NSFileManager *mgr = [NSFileManager defaultManager];
NSArray *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentPath objectAtIndex:0];
NSString *dstPath = [documentDirectory stringByAppendingPathComponent:#"MultiSetting.plist"];
bundle = [NSBundle mainBundle];
NSString *srcPath = [bundle pathForResource:#"MultiSetting" ofType:#"plist"];
NSError *error = nil;
[mgr copyItemAtPath:srcPath toPath:dstPath error:(NSError **)error];
settingArray = [[NSMutableArray alloc] initWithContentsOfFile:dstPath];
NSLog(#"%#", settingArray);
Is there any solution to solve this problem? Did I do anything wrong?
First thing you are passing error wrong. You need to change that line to
[mgr copyItemAtPath:srcPath toPath:dstPath error:&error];
Another thing according to your code after the first run, the above line will fail as there will already be a file that particular name. If you want to initialize only once then I guess you could write something like below which makes more sense:
NSError *error = nil;
if (![mgr fileExistsAtPath:dstPath]) {
[mgr copyItemAtPath:srcPath toPath:dstPath error:&error];
if (error) {
NSLog(#"%#",[error localizedDescription]);
}
}
And finally initWithContentsOfFile: might be failing because the plist cannot be parsed into an array. This can be because your plist file's root object is a dictionary (the default when you create the plist using Xcode).
Since you are able to parse the plist file in bundle it might be because, you might have accidentally copied the wrong file (or an empty file or a plist with root as dictionary) the first time and then was not able to copy. So try deleting the file from dstPath and try again.
To check the file do an NSLog of dstPath. For example if you get something like this in the console:
/Users/xxxx/Library/Application Support/iPhone Simulator/5.0/Applications/194351E6-C64E-4CE6-8C82-8F66C8BFFAAF/Documents/YourAppName.app
Copy this till the Documents folder, i.e
/Users/xxxx/Library/Application Support/iPhone Simulator/5.0/Applications/194351E6-C64E-4CE6-8C82-8F66C8BFFAAF/Documents/
Go to:
Finder -> Go -> Go To Folder
and paste the path and click on Go. This should take you to the actual directory and check the contents of the plist . Open it as source code in Xcode to see what is the root object.
Also try deleting this file you find here and running your app agin.
Another way to achieve the above would be to initialize the array from bundle and then write it directly instead of copying the file (but this is not a the direct answer to your problem just a workaround) i.e :
NSString *srcPath = [bundle pathForResource:#"MultiSetting" ofType:#"plist"];
NSError *error = nil;
//initialize from source
NSMutableArray *settingsArray = [[NSMutableArray alloc] initWithContentsOfFile:srcPath];
//write to file
NSError *error = nil;
//check if file exists
if (![mgr fileExistsAtPath:dstPath]) {
[settingArray writeToFile:dstPath atomically: YES];
if (error) {
NSLog(#"%#",[error localizedDescription]);
}
}
Any changes you make to the settingArray array do not automatically get saved to disk. You need to explicitly save it to disk. When you actually want to save the contents of the settingArray variable, you need to call:
[settingArray writeToFile:dstPath atomically: YES];

Where do I need to release the memory in iOS without ARC? [duplicate]

This question already has answers here:
Understanding reference counting with Cocoa and Objective-C
(14 answers)
Closed 9 years ago.
I have a method and don't use ARC:
-(void)readAppPlist
{
NSString *plistPath = [self getDataFileDestinationPath];
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSPropertyListFormat format;
NSMutableDictionary *temp = (NSMutableDictionary *) [NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
if (!temp) {
NSLog(#"Error reading plist: %#, formatL %d", errorDesc, format);
}
items = [[temp objectForKey:#"Items"] mutableCopy];
}
According to the rules of memory management where do I need to release the memory for the variables plistPath, plistXML, errorDesc, temp? Shall I writh another one method, release them here or just put them into the dealloc global method for this class?
Assuming you followed naming conventions correctly (and that is probably a pretty big leap), the answers would be:
plistPath : No
plistXML : No
errorDesc : No
temp : No
The rule is if you alloc copy or retain it, you must release it (new counts as alloc)

iOS Efficiently Parse Large JSONs from Documents Dir

When I load my app I download around 10-15 JSON files and store them into my apps documents directory-- some range from a few KBs to 30MB.
Once that is finished, I need to grab each of them from the documents dir, convert to a NSDictionary, and parse into NSManagedObjects.
But, when I do that with the code below, as it goes though each JSON it seems to keep them in memory, until the app ends up crashing. Instruments shows nothing in the 'Leaks' tool, but my app is keeping a ton in memory.
Heres the code that grabs the JSON files:
UPDATED
- (void)parseDownloadedFiles
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
docDir = [docDir stringByAppendingString:#"/jsons"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *files = [fileManager contentsOfDirectoryAtPath:docDir error:&error];
if (files == nil) {
// error...
}
for (NSString *file in files)
{
NSString *fileName = [NSString stringWithFormat:#"%#/jsons/%#",
docDir, file];
NSString *content = [[NSString alloc] initWithContentsOfFile:fileName
usedEncoding:nil
error:nil];
NSDictionary *JSON =
[NSJSONSerialization JSONObjectWithData: [content dataUsingEncoding:NSUTF8StringEncoding]
options: NSJSONReadingMutableContainers
error: &error];
...create my NSManagedObjects & store
JSON = nil;
}
}
--
Heres a look at my allocations:
--
Drilling into that first Malloc 44.79 brings shows me these problem lines:
--
This is within the for loop in the code above
Would that NSLog really cause such an issue?
You should put #autoreleasepool {} inside that loop where you read the file. The objects are not being released until the method returns, so the memory will build up inside the loop.
ARC will help you by autoreleasing the objects but you need them to release faster.
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html
Yep, your getContentFromFile... method is returning a retained string. And you never release it on the receiving end.
You need to either autorelease the string when you return it or explicitly release if after you've parsed it into JSON.
(I'd think Analyzer would have found this.)

Resources