editing plist file programtically

Discussion in 'Mac Programming' started by McBgnr, May 4, 2009.

  1. McBgnr macrumors regular

    Joined:
    Apr 13, 2009
    #1
    Hello,

    I am saving my application prefernces into a plist file. I want to be able to edit the preferences through my program.

    I have tried using NSMutableDictionary and NSMutableArray as in:
    Code:
    //Read the preferences from the PLIST file to the local cache
    	NSMutableDictionary *appPreferences = [NSMutableDictionary dictionaryWithContentsOfFile:[PREFERENCES_FILE stringByExpandingTildeInPath]];
    	NSMutableArray *appPrefKeys = [appPreferences allKeys];
    
    However, I am getting warning saying "initialization from distinct Objective C type". How can I solve this warning? Is there some other way of editing the plist file thru program?
     
  2. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #2
    1) Why are you not using NSUserDefaults which is provided for this purpose (saving user preferences)

    2) The allKeys method returns a NSArray, not a NSMutableArray
     
  3. McBgnr thread starter macrumors regular

    Joined:
    Apr 13, 2009
    #3
    Thanks for the helpful response.

    I am very sure if I can use NSUserDefaults in my case. Because I want to store some application execution related data into the Plist file and want to read this data from the plist file during execution of the program. Some paths in the program will change the data in this plist file, so I want to be able to edit/insert data to plist. I will not be having any GUI components for interacting with the plist file. pls suggest if NSUserDefaults can still be used?
     
  4. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #4
    I fail to see why any of that matters: NSUserDefaults is designed to support the storing, retrieving and use (store, retrieve, alter, delete) at run-time of per-user application data. Unless you are storing huge amounts of data (in which case you shouldn't be using a plist of any sort) it is the correct answer for this.
     
  5. McBgnr thread starter macrumors regular

    Joined:
    Apr 13, 2009
    #5
    Oh I See... it looks like that I should be using some other file format for saving this data. The data will vary from user to user and can be lots in some cases. Very much like how the browsers maintain user cache info etc.

    Any suggestions on better file formats for this purpose?
     
  6. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #6
    Depends on what the data is. If you look at a browser cache it uses lots of files (thousands normally) with different formats: text, image etc...
     
  7. McBgnr thread starter macrumors regular

    Joined:
    Apr 13, 2009
    #7
    Thanks a bunch. Will check if I can use some other file format for saving my data.

    Am just getting a bit curious ... any idea on the max size (ideal) limit for PLIST file?
     
  8. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #8
    As far as I am aware there is no hard limit on the size of a plist (beyond what the filesystem imposes), but I feel that if it's getting to be above a few hundred KB then it's probably too big: either too many keys or binary data that should be stored in it's own file. For simply stored basic key/value pairs you should be able to store thousands before it gets anything like that big...
     
  9. McBgnr thread starter macrumors regular

    Joined:
    Apr 13, 2009
    #9
    Thanks for the helpful reply.

    It looks like that I can still make use of a plist file for storing the data if it can go upto a few hundred KBs. However, is it possible to edit the contents without using NSUserDefaults or to specify an input file name for NSUserDefaults?
     
  10. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #10
    You can use the methods of NSArray or NSDictionary (or their Mutable counterparts) to read/write from any file you want. Which one you choose depends on what you want at the root of your plist. But if this data in any way represents user preferences or similar you should be using NSUserDefaults: it guarantees the file is saved in the correct location on the file system, has the correct name, works correctly with network home directories etc.
     
  11. McBgnr thread starter macrumors regular

    Joined:
    Apr 13, 2009
    #11
    Finally I have been able to use NSMutableDictionary and NSMutableArray to edit an existing plist file.

    The original plist file contents are:
    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    	<key>Days</key>
    	<array>
    		<string>Monday</string>
    		<string>Tuesday</string>
    	</array>
    	<key>Months</key>
    	<array>
    		<string>Jan</string>
    		<string>Feb</string>
    	</array>
    </dict>
    </plist>
    

    The code that I have used to update and existing array and insert a new array into this plist file is:
    Code:
    	NSMutableDictionary *testDict = [[[NSMutableDictionary alloc] initWithContentsOfFile:[@"~/Library/Test.plist" stringByExpandingTildeInPath]] autorelease];
    	NSEnumerator *testEnumerator = [[NSEnumerator alloc] autorelease];
    	testEnumerator = [testDict objectEnumerator];
    	
    	NSMutableArray *testArray ;
    	while(testArray = [testEnumerator nextObject])
    	{
    		for(int i = 0; i < [testArray count]; i++)
    		{
    			NSLog([testArray objectAtIndex:i]);
    		}
    		
    		if(testArray == [testDict objectForKey:@"Months"])
    		{
    			[testArray addObject:@"Mar"];
    		}
    	}
    	
    	NSMutableArray *newArray = [[[NSMutableArray alloc] init] autorelease];
    	[newArray addObject:@"2000"];
    	[newArray addObject:@"2004"];
    	
    	[testDict setObject:newArray forKey:@"Years"];
    	
    	[testDict writeToFile:[@"~/Library/Test.plist" stringByExpandingTildeInPath] atomically:TRUE];
    
    The code is working fine. I have posted it here for review and suggestions on improving it (in case it can be improved). Pls suggest.
     
  12. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #12
    Code:
    NSEnumerator *testEnumerator = [[NSEnumerator alloc] autorelease];
    testEnumerator = [testDict objectEnumerator];
    
    Since you sent the new object autorelease, this won't leak, but it's not necessary. You assign the pointer to a new enumerator to testEnumerator, and on the very next line you assign a totally different enumerator. Just get rid of the first line, and declare testEnumerator on the 2nd instead.

    Code:
    if(testArray == [testDict objectForKey:@"Months"])
    
    Sadly, this works, but i think it is bad form. Others may have a different opinion, but i think you should use isEqualToArray: to compare arrays, not compare pointers. Again, in this very specific case the pointers will be the same, but this is a dangerous habit to develop.

    Code:
    for(int i = 0; i < [testArray count]; i++)
    
    If you know, absolutely, that every object is an array (or they all at least respond to count and objectAtIndex: ), this isn't terrible, but you're really making this "niche" code by assuming that every object in the dictionary is an array. You may want to at least ask if the object respondsToSelector:, or better, ask if it isKindOfClass:[NSArray class]
    http://developer.apple.com/DOCUMENT...apple_ref/occ/intfm/NSObject/isMemberOfClass:

    Once you're sure, you can send these messages.

    Otherwise, it looks good. If you are using 10.5 or above, i would recommend the for...in syntax instead of bothering with a special enumerator, but that's not necessary.

    -Lee

    EDIT: Thought this through more, and since you want to do something special for one particular key, it seems better to use for...in or an enumerator over they keys instead. It's inconsequential to get a value when you have the key, and you can just check isEqualToString between the desired key and the current key, instead of having to compare the arrays.
     
  13. McBgnr thread starter macrumors regular

    Joined:
    Apr 13, 2009
    #13
    Thanks a lot lee1210. These are wonderful suggestions. The links are also very helpful.
     

Share This Page