Navigating plists and settings objects - iphone

I have a resource file (plist) added to my project. I am currently coding some sort of a "helper" for reading and writing to it. I have 3 get and 3 set methods. First one returns an object at the top, second one returns object which is inside of another dictionary (see code) and the third one returns an object at any given depth I just have to specify the node names so it can get there. (I hope you can understand me)
The problem comes with setters. Setting an "surface" object is no big deal so is setting an object that is in another dictionary. The problem comes when I try to set an object at a depth.
Before I write anything else I will post the code so you can understand what I'm saying.
fullContent is a NSMutableDictionary containing the file.
//This one is easy, just return the object for the key.
- (id)getSurfaceObjectForKey:(NSString*)key
{
return [fullContent objectForKey:key];
}
//Hope you understand this one. Main parent is a string with the name of the first node. It gets a dictionary out of my plist and returns an object for key (I have a dictionary structured plist)
- (id)getMainParentChildObjectForKey:(NSString*)key
{
NSAssert(!mainParent, #"Main parent must not be nil");
return [[fullContent objectForKey:mainParent] objectForKey:key];
}
//This one gets the element at any given depth I just have to pass in an array containing node names
- (id)getObjectForKey:(NSString *)key atDepthWithChildren:(NSArray *)children
{
id depthElement = fullContent;
for (int i = 0; i < children.count; i++)
depthElement = [depthElement objectForKey:[children objectAtIndex:i]];
return [depthElement objectForKey:key];
}
//Sets a top (surface) object
- (void)setSurfaceObject:(id)object ForKey:(NSString *)key
{
[fullContent setObject:object forKey:key];
[self writePlistContent];
}
//Sets an object inside a dictionary (mainParent - string with the name of dictionary node)
- (void)setMainParentChildObject:(id)object forKey:(NSString *)key
{
[[fullContent objectForKey:mainParent] setObject:object forKey:key];
[self writePlistContent]; //Self explanatory. I write this to file
}
//This is where my problem comes. How do I save this to plist without making any other changes to it? Im guessing I have to rebuild it from inside up?
- (void)setObject:(id)object forKey:(NSString *)key atDepthWithChildren:(NSArray *)children
{
id depthElement = fullContent;
for (int i = 0; i < children.count; i++)
depthElement = [depthElement objectForKey:[children objectAtIndex:i]];
[depthElement setObject:object forKey:key]; //I set the desired object but I dont know how to save it
for (int i = 0; i < children.count - 1; i++)
{
//Here i guess i would have to build the NSDictionary from inside out. Using a NSMutable array perhaps?
}
}
I hope you understand my problem. I hope Im not complicating things too much. Im just really tired and have been up for nearly 24 hours now and cant think of a way to solve this.
Thank you in advance.

I don't understand why you don't just use your:
[self writePlistContent];
to save it.
Surely it will save the entire contents of the plist.

Related

iPhone Table View: Making Sections, UILocalizedIndexedCollation selector

I'm having trouble making the sections in a UITableView. I've looked at the documentation for UILocalizedIndexedCollation as well as this sample code project:
https://developer.apple.com/library/ios/#samplecode/TableViewSuite/Listings/3_SimpleIndexedTableView_Classes_RootViewController_m.html#//apple_ref/doc/uid/DTS40007318-3_SimpleIndexedTableView_Classes_RootViewController_m-DontLinkElementID_18
What I have below is basically a straight copy/paste from the sample project. However, the sample project uses a custom object (TimeZoneWrapper.h) and then places the object in the correct section based on the object's instance variable (TimeZoneWrapper.localeName). However, I'm not using custom objects. I'm using just a bunch of regular NSStrings. So my question is what method on NSString should I pass to the #selector() to compare and place the string in the correct section array?
Currently, I'm calling NSString's copy method as a temporary hack to get things working (which it does), but I'm not sure if this is correct. A little explanation would be much appreciated!
- (void)configureSections {
// Get the current collation and keep a reference to it.
self.collation = [UILocalizedIndexedCollation currentCollation];
NSInteger index, sectionTitlesCount = [[collation sectionTitles] count]; // sectionTitles are A, B, C, etc.
NSMutableArray *newSectionsArray = [[NSMutableArray alloc] initWithCapacity:sectionTitlesCount];
// Set up the sections array: elements are mutable arrays that will contain the locations for that section.
for (index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *array = [[NSMutableArray alloc] init];
[newSectionsArray addObject:array];
}
// Segregate the loctions into the appropriate arrays.
for (NSString *location in locationList) {
// Ask the collation which section number the location belongs in, based on its locale name.
NSInteger sectionNumber = [collation sectionForObject:location collationStringSelector:#selector(/* what do I put here? */)];
// Get the array for the section.
NSMutableArray *sectionLocations = [newSectionsArray objectAtIndex:sectionNumber];
// Add the location to the section.
[sectionLocations addObject:location];
}
// Now that all the data's in place, each section array needs to be sorted.
for (index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *locationsArrayForSection = [newSectionsArray objectAtIndex:index];
// If the table view or its contents were editable, you would make a mutable copy here.
NSArray *sortedLocationsArrayForSection = [collation sortedArrayFromArray:locationsArrayForSection collationStringSelector:#selector(/* what do I put here */)];
// Replace the existing array with the sorted array.
[newSectionsArray replaceObjectAtIndex:index withObject:sortedLocationsArrayForSection];
}
self.sectionsArray = newSectionsArray;
}
Thanks in advance!
You should use #selector(self).
Using #selector(copy) will cause memory leaks in your project

objective c perform selector in background and autoreleasepool

I am developing an iphone application which has some data stored in a sqllite database. When my view loads i would like to load the data from the database on a background thread. The problem is the application keeps crashing and i dont know why.
The code:
-(id) init
{
if((self=[super init]))
{
[self performSelectorInBackground:#selector(loadList) withObject:nil];
}
}
-(void) loadList
{
#autoreleasepool
{
Loader * loader = [[Loader alloc] init];
NSMutableArray * array = [loader getItemList];
[array retain];
NSLog(#"Got %d items",[array count]);
[self performSelectorOnMainThread:#selector(createList:) withObject:array waitUntilDone:false];
[loader release];
}
}
-(void) createList: (NSMutableArray*) array
{
items = array;
int i;
Item * it;
for(i = 0; i < [items count]; i++)
{
it = [items objectAtIndex: i];
[it getName]; // crashes
// populate the list
}
}
Loader returns a NSMutableArray with Item objects. The application crashes when i call the item getName (which returns a NSString*). From what i understand it crashes because the item name properties is being released. What am i doing wrong?
Thanks!
It's likely to be a problem with whatever type of object you're using to populate array.
I'm unable to find finger-on-paper proof but I'm confident that performSelectorOnMainThread:withObject:waitUntilDone: retains its object. However if each of the items in array keeps a reference to loader then they need to take responsibility for retaining that object. It looks like you're attempting to keep it alive manually but — as Chuck alludes to — your call to performSelector... will return instantly and not wait for the call you've made to complete.
This particular bug appears to be that you're passing waitUntilDone:NO, so the array is being released immediately and consequently so are its items.
But in general, UIKit is not thread-safe, so this is just a touchy design. I would probably put the loading of this stuff in another class that handles the task for you instead of right in the view.
I'd put a breakpoint on the line:
it = [items objectAtIndex: i];
Then type
po it
in the debugger, and see what's in the name field. As a guess, I'd say one of two things: 1) the field that getName returns isn't initialized with an object (i.e. isn't a real NSString *) or that you're getting a C string from SQLite (which is what it usually returns) and you're trying to treat it as an NSString *. If it's the latter you can use [myCString stringWithUTF8String] to convert the C string into an NSString *

Building NSObject to save ABRecordRef data local

I want to store the Content of a ABRecordRef in my own Application independent of the entry in the AdressBook. After crossreading through stackoverflow and the apple developers site i found this to be the best way:
If got that right, one would need a NSCoding conform NSObject subclass with all values of ABRecordRef and at least the functions initWithABRecordRef and getABRecordRefFromPeopleData (assuming one names his class peopleData), where initWithABRecordRef would fill all values from an ABRecordRef (if not null) into an instance of the own class and getABRecordRefFromPeopleData returns an ABRecordRef opaque type with the Data stored in an instance of the own class.
My question to that:
I wonder if someone out there already did that, because I can imagine, other people came to the exact same problem before. If so, getting hands on that class would be aewsome, if not i am going to give it a try on my own and load the results up here if wanted.
Maybe you even know a better way to do that?
If you did implement a solution, please share it. If you need the same, I'd appreciate working that out together.
Edit:
I now started working on the thing and (as i expected) i ran into some unclear problems.
As it comes to kABStringPropertyType values my concept is pretty straight forward. Small example:
#implementation RealRecord
#synthesize firstName; //NSString
- (id)initWithRecordRef:(ABRecordRef)record{
//Single valued entrys
NSString *contactName = (NSString *)ABRecordCopyValue(record, kABPersonFirstNameProperty);
firstName=contactName;
[contactName release];
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder{
//single value entrys
self.firstName= [aDecoder decodeObjectForKey:#"firstName"];
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:firstName forKey:#"firstName"];
}
- (ABRecordRef)returnABRecordRefFromRealRecord{
ABRecordRef returnRecord =ABPersonCreate();
ABRecordSetValue(returnRecord, kABPersonFirstNameProperty, firstName, NULL);
return returnRecord;
}
This works fine. Now im not so clear with how to do the same thing with the kABMultiStringPropertyType.I made me a NSMutableArray emailList and went on like this:
- (id)initWithRecordRef:(ABRecordRef)record{
//Multi valued entrys
ABMutableMultiValueRef email=ABRecordCopyValue(record, kABPersonEmailProperty);
for (int i = 0; i < ABMultiValueGetCount(email); i++) {
NSString *anEmail = [(NSString*)ABMultiValueCopyValueAtIndex(email, i) autorelease];
[emailList addObject:anEmail];
}
return self;
}
Now im not shure, how to do the rest of the tasks on the multi values. Can i just encode and decode the NSMutableArray like i did with the Strings or do i have to set a key for all of the emails in emailList?
And how do i get the damn thing back into the returnRecord?
Look into using libsqlite3.dylib and creating a sql database that will access the flat files generated for all the properties.
Sample iOS project with sqlite3 library is here:
http://www.techotopia.com/index.php/An_Example_SQLite_based_iOS_7_Application
&
Detail on doing so with ABAddressbook for contact's multi-value properties here:
http://linuxsleuthing.blogspot.com/2012/10/addressing-ios6-address-book-and-sqlite.html

Core Data object into an NSDictionary with possible nil objects

I have a core data object that has a bunch of optional values. I'm pushing a table view controller and passing it a reference to the object so I can display its contents in a table view. Because I want the table view displayed a specific way, I am storing the values from the core data object into an array of dictionaries then using the array to populate the table view. This works great, and I got editing and saving working properly.
(i'm not using a fetched results controller because I don't have anything to sort on)
The issue with my current code is that if one of the items in the object is missing, then I end up trying to put nil into the dictionary, which won't work.
I'm looking for a clean way to handle this, I could do the following, but I can't help but feeling like there's a better way.
*passedEntry is the core data object handed to the view controller when it is pushed, lets say it contains firstName, lastName, and age, all optional.
if ([passedEntry firstName] != nil) {
[dictionary setObject:[passedEntry firstName] forKey:#"firstName"]
}
else {
[dictionary setObject:#"" forKey:#"firstName"]
}
And so on. This works, but it feels kludgy, especially if I end up adding more items to the core data object down the road.
What you could do is iterate through all of the object's properties using the objc_* runtime functions like so:
unsigned int property_count;
objc_property_t * prop_list = class_copyPropertyList([CoreDataObject class], &property_count);
for(int i = 0; i < property_count; i++) {
objc_property_t prop = prop_list[i];
NSString *property_name = [NSString stringWithCString:property_getName(prop) encoding:NSUTF8StringEncoding];
id obj = [passedEntry valueForKey:property_name];
[dictionary setObject:((obj != nil) ? obj : [NSNull null]) forKey:property_name];
}
free(prop_list);

iPhone SDK: updating objects in an NSArray

I have an NSArray of (Product) objects that are created by parsing an XML response from a server.
In the object, it has images, and text, and ints, URLs. etc.
There are 2 requests to the server
1: list of matching products from a search - small amount of detail
2: product details: the full details.
When the second request is parsed I am trying to update the existing object in the array.
- (void) setProduct:(Product *) _product atIndex: (int) index
{
[_product retain];
[productList replaceObjectAtIndex:index withObject:_product];
}
This doesn't seem to work as when I call update and table reloadData, the new values are not present.
Should I remove the object in the array first?
replaceObjectAtIndex: is a method of NSMutableArray. So you would need to do make your productLists a NSMutableArray to use it.
-(void)updateprevious:(int)index withArg2:(NSString *)date
{
NSLog(#"%#",date);
NSLog(#"%d",index);
for (int i=0;i < index; i++)
{
[final_X replaceObjectAtIndex:i withObject:#""];
}
}
You'll have to post more code from your data source methods. What you are doing here should work fine.
Your "retain" method is unnecessary, you're leaking _product.