I have the following code that parses json into custom objects, parsing works fine i have a problem with saving the objects.
-(void)handleCities:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"_getInitialData" object:nil];
if(![notification object])
{
NSLog(#"Something went wrong");
return;
}
// Convert data to a dictionary
NSMutableDictionary * data = [NSJSONSerialization JSONObjectWithData:(NSMutableData *) [notification object] options:NSJSONReadingMutableContainers error:nil];
// Loop over all the data
if([data objectForKey:#"data"] != (id)kCFBooleanFalse)
{
for(int i = 0; i < [[[data objectForKey:#"data"] objectForKey:#"cities"] count]; i++)
{
CityObject * object = [[CityObject alloc] init];
// Loop over all the properties
for(NSString * key in [[[data objectForKey:#"data"] objectForKey:#"cities" ] objectAtIndex:i])
{
NSString * _key = key;
if([object._remap objectForKey:key])
{
NSString * value = [object._remap objectForKey:_key];
NSLog(#"Notice [%#] Remapping \"%#\" to \"%#\"", self.class, _key, value);
_key = value;
}
// Save the value for easy access
NSString * value = [[[[data objectForKey:#"data"] objectForKey:#"cities" ] objectAtIndex:i] objectForKey:_key];
// Does the value have a value
if((id)value == [NSNull null])
{
value = #"";
}
// Set the value for key
[object setValue:value forKey:_key];
}
NSMutableDictionary * set = [[[data objectForKey:#"data"] objectForKey:#"cities"] objectAtIndex:i];
int count = (int)[[set objectForKey:#"categories"] count];
for(int b = 0; b < count; b++)
{
CategoryObject * category = [[CategoryObject alloc] init];
for(NSString * key in [[set objectForKey:#"categories"] objectAtIndex:b])
{
NSString * value = [[[set objectForKey:#"categories"] objectAtIndex:b] objectForKey:key];
if((id)value == [NSNull null])
{
value = #"";
}
[category setValue:value forKey:key];
}
NSLog(#"Category %#",category.class);
NSLog(#"Object : %#",object.class);
NSLog(#"Object Categories : %#",object.categories.class);
// i can print the category object fine here
[object.categories addObject:(CategoryObject *)category];
}
[self.cities addObject:object];
}
}
CityObject * city = [self.cities objectAtIndex:0];
CategoryObject * category = [city.categories objectAtIndex:0];
NSLog(#"Category Name : %#", category.name);
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"_getInitialData" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"getInitialData" object:nil];
}
CityObject.h
#import <Foundation/Foundation.h>
#import "Object.h"
#interface CityObject : Object
#property (nonatomic, strong) NSString * name;
#property (nonatomic, strong) NSString * id;
#property (nonatomic, strong) NSString * state;
#property (nonatomic, strong) NSString * image;
#property (nonatomic, strong) NSString * term_order;
#property (nonatomic, strong) NSMutableArray * categories;
#property (nonatomic, strong) NSMutableDictionary * _remap;
#end
CityObject.m
#import "CityObject.h"
#implementation CityObject
-(id)init
{
if(self = [super init])
{
self._remap = [[NSMutableDictionary alloc] init];
self.categories = [[NSMutableArray alloc] init];
[self._remap setObject:#"state" forKey:#"new_city"];
}
return self;
}
#end
CategoryObject.h
#import <Foundation/Foundation.h>
#import "Object.h"
#interface CategoryObject : Object
#property (nonatomic, strong) NSString * name;
#property (nonatomic, strong) NSString * icon;
#property (nonatomic, strong) NSString * id;
#end
Before i add the CategoryObject to my array i can retrieve the values without a problem it is when i NSLog at the end i cannot get the values / objects out of the array's.
What am i doing wrong since it works great when i add the CityObjects but not when i "parse" the categories. I can read them fine before i add them to the object.categories
Error / Output
-[__NSDictionaryM name]: unrecognized selector sent to instance 0x7a482760
2015-03-24 09:21:05.649 10things[16968:337325] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryM name]: unrecognized selector sent to instance 0x7a482760'
Problem solved, i did not have a strong pointer to the model, the Dictionary was magically being replaced to a NSMutableDictionary Once i had a strong handle everything worked properly. (thanks for the code feedback)
Related
my object CCategory.h
#interface CCategory : NSObject
#property(strong, nonatomic) NSNumber * _Nonnull categoryId;
#property(strong, nonatomic) NSNumber * _Nonnull originalId;
#property(strong, nonatomic) NSString * _Nonnull name;
#property(strong, nonatomic) NSString * _Nonnull type;
#property(nonatomic, strong) CCategory * _Nullable parent;
#property (nullable, nonatomic, retain) NSOrderedSet<CCategory *> *children;
- (instancetype _Nonnull )initWithId:(NSNumber *_Nullable)categoryId
andOriginalId:(NSNumber *_Nullable)originalId
andName:(NSString *_Nonnull)name
andType:(NSString *_Nonnull)type
andParent:(CCategory *_Nullable)parent
andChildren:(NSOrderedSet<CCategory *> *_Nullable)children NS_DESIGNATED_INITIALIZER;
#end
CCategory.m
#implementation CCategory
- (instancetype)init {
return [self initWithId:0 andOriginalId:0 andName:#"" andType:#"" andParent:nil andChildren:nil];
}
- (instancetype)initWithId:(NSNumber *)categoryId
andOriginalId:(NSNumber *)originalId
andName:(NSString *)name
andType:(NSString *)type
andParent:(CCategory *)parent
andChildren:(NSOrderedSet<CCategory *> *)children {
self = [super init];
if (self) {
self.categoryId = categoryId;
self.originalId = originalId;
self.name = name;
self.type = type;
self.parent = parent;
self.children = children;
}
return self;
}
#end
This is how I check class type:
CCategory * firstItem = [itemsArray objectAtIndex:0];
CCategory *child = [firstItem.children objectAtIndex:0];
NSString *className = NSStringFromClass([child class]);
NSLog(#"First Item is: %#", className);
firstItem returns type CCategory, child returns type NSDictionary
After receiving from database object contains all data, but children for some reason is the NSDictionary type, not CCategory class type. Why is that? and how can I make children type CCategory?
Because you declare some object of some class doesn't mean that it's of the correct class.
If you write for instance
NSArray *array = [#[#"Hello"] firstObject];
array will be in fact a NSString object.
So, when you parse your response and create your CCategory object from what I guess a NSDictionary object.
That's why children seems to be in fact an NSOrderedSet of NSDictionary and not of CCategory objects.
A possible way to do it, is to call recursively initWithId:andOriginalId:andName:andType:andParent:andChildren: for the children.
So instead of self.children = children;
NSMutableOrderedSet *childrenSet = [[NSMutableOrderedSet alloc] init];
for (NSDictionary *aChildDict in [children array])
{
CCategory *aChild = [CCategory alloc] initWithId:aChildDict[keyWhereThereIsID], etc.]
[childrenSet addObject:aChild];
}
self.children = childrenSet;
But that's more of a hack to set it like that in the init method, because it says children should be NSOrderedSet<CCategory *> *.
So it's up to you, to either rename the method to be clear of what it does and maybe accept a NSOrderedSet<NSDictionary *> * for children instead, parse it before, create another one, etc.
One possible lazy option is to do that:
Rename to andChildren:(NSOrderedSet *)children
NSMutableOrderedSet *childrenSet = [[NSMutableOrderedSet alloc] init];
for (id *aChildObject in [children array])
{
CCategory *aChild = nil;
if ([aChildObject isKindOfClass:[NSDictionary class]]) //Need parsing
{
NSDictionary *aChildDict = (NSDictionary *)aChildObject;
aChild = [CCategory alloc] initWithId:aChildDict[keyWhereThereIsID], etc.];
}
else if ([aChildObject isKindOfClass:[CCategory class]]) //Already a CCategory Object
{
aChild = (CCategory *)aChildObject;
}
else
{
NSLog(#"Ooops, child of wrong class: %#", NSStringFromClass([aChildObject class]);
}
if (aChild) { [childrenSet addObject:aChild]; }
}
self.children = childrenSet;
I've been trying to store and recover from the NSUserDefaults an Array of custom objects in my project but it seems something is going wrong. I've researched a lot about this issue but I couldn't find any answer to my problem.
It seems that the storing it's done OK because I don't get any error when saving data in NSUserDefaults, the problem comes when I try to get back those data: the app will crash completely with libc++abi.dylib: terminating with uncaught exception of type NSException error.
Here's my code:
Wine.h
#import <Foundation/Foundation.h>
#import UIKit;
#define NO_RATING -1
#interface WineModel : NSObject <NSCoding>
#property(copy, nonatomic) NSString *type;
#property(strong, nonatomic) UIImage *photo;
#property(strong, nonatomic) NSURL *photoURL;
#property(strong, nonatomic) NSURL *wineCompanyWeb;
#property(copy, nonatomic) NSString *notes;
#property(copy, nonatomic) NSString *origin;
#property(nonatomic) int rating;
#property(strong, nonatomic) NSArray *grapes;
#property(copy, nonatomic) NSString *name;
#property(copy, nonatomic) NSString *wineCompanyName;
- (id) initWithCoder: (NSCoder *) decoder;
- (void) encodeWithCoder: (NSCoder *) encoder;
//SOME OTHER METHODS...//
-(id) initWithName: (NSString *) aName
wineCompanyName: (NSString *) aWineCompanyName
type: (NSString *) aType
origin: (NSString *) anOrigin
grapes: (NSArray *) arrayOfGrapes
wineCompanyWeb: (NSURL *) aURL
notes: (NSString *) aNotes
rating: (int) aRating
photoURL: (NSURL *) aPhotoURL;
//For JSON
-(id) initWithDictionary: (NSDictionary *) aDict;
#end
Wine.m
#import "WineModel.h"
#implementation WineModel
#synthesize photo = _photo;
#pragma mark - Properties
-(UIImage *) photo {
//SOME MORE CODE...
return _photo;
}
- (id) initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
self.name = [decoder decodeObjectForKey:#"name"];
self.wineCompanyName = [decoder decodeObjectForKey:#"company"];
self.type = [decoder decodeObjectForKey:#"type"];
self.origin = [decoder decodeObjectForKey:#"origin"];
self.grapes = [self extractGrapesFromJSONArray:[decoder decodeObjectForKey:#"grapes"]];
self.wineCompanyWeb = [NSURL URLWithString:[decoder decodeObjectForKey:#"wine_web"]];
self.notes = [decoder decodeObjectForKey:#"notes"];
self.rating = [[decoder decodeObjectForKey:#"rating"] intValue];
self.photoURL = [NSURL URLWithString:[decoder decodeObjectForKey:#"picture"]];
}
return self;
}
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeObject:self.wineCompanyWeb forKey:#"company"];
[encoder encodeObject:self.type forKey:#"type"];
[encoder encodeObject:self.origin forKey:#"origin"];
[encoder encodeObject:self.grapes forKey:#"grapes"];
[encoder encodeObject:self.wineCompanyWeb forKey:#"wine_web"];
[encoder encodeObject:self.notes forKey:#"notes"];
[encoder encodeInt:self.rating forKey:#"rating"];
[encoder encodeObject:self.photoURL forKey:#"picture"];
}
#pragma mark - Init
-(id) initWithName: (NSString *) aName
wineCompanyName: (NSString *) aWineCompanyName
type: (NSString *) aType
origin: (NSString *) anOrigin
grapes: (NSArray *) arrayOfGrapes
wineCompanyWeb: (NSURL *) aURL
notes: (NSString *) aNotes
rating: (int) aRating
photoURL: (NSURL *) aPhotoURL {
if(self==[super init]) {
_name = aName;
_wineCompanyName = aWineCompanyName;
_type = aType;
_origin = anOrigin;
_grapes = arrayOfGrapes;
_wineCompanyWeb = aURL;
_notes = aNotes;
_rating = aRating;
_photoURL = aPhotoURL;
}
return self;
}
#pragma mark - JSON
-(id) initWithDictionary:(NSDictionary *)aDict {
return [self initWithName:[aDict objectForKey:#"name"]
wineCompanyName:[aDict objectForKey:#"company"]
type:[aDict objectForKey:#"type"]
origin:[aDict objectForKey:#"origin"]
grapes:[self extractGrapesFromJSONArray:[aDict objectForKey:#"grapes"]]
wineCompanyWeb:[NSURL URLWithString:[aDict objectForKey:#"wine_web"]]
notes:[aDict objectForKey:#"notes"]
rating:[[aDict objectForKey:#"rating"]intValue]
photoURL:[NSURL URLWithString:[aDict objectForKey:#"picture"]]
];
}
-(NSArray *) extractGrapesFromJSONArray: (NSArray *)JSONArray {
//SOME MORE CODE...
return grapes;
}
#end
This is the wine class. It has the <NSCoding> protocol and both methods (id) initWithCoder: (NSCoder *) decoder; and (void) encodeWithCoder: (NSCoder *) encoder;. So far I looks OK, lets move on to the next class:
Winery.h
#import <Foundation/Foundation.h>
#import "Wine.h"
#define RED_WINE_KEY #"Red"
#define WHITE_WINE_KEY #"White"
#define OTHER_WINE_KEY #"Others"
#interface WineryModel : NSObject
#property (strong, nonatomic) NSMutableArray *redWines;
#property (strong, nonatomic) NSMutableArray *whiteWines;
#property (strong, nonatomic) NSMutableArray *otherWines;
#property(readonly, nonatomic) int redWineCount;
#property(readonly, nonatomic) int whiteWineCount;
#property(readonly, nonatomic) int otherWineCount;
-(WineModel *) redWineAtIndex: (NSUInteger) index;
-(WineModel *) whiteWineAtIndex: (NSUInteger) index;
-(WineModel *) otherWineAtIndex: (NSUInteger) index;
#end
Winery.m
#import "Winery.h"
#implementation WineryModel
#pragma mark - Properties
-(int) redWineCount {
return [self.redWines count];
}
-(int) whiteWineCount {
return [self.whiteWines count];
}
-(int) otherWineCount {
return [self.otherWines count];
}
-(id) init {
if(self == [super init]) {
NSUserDefaults *userDefault=[NSUserDefaults standardUserDefaults];
//Check if there is data stored locally
if(([[[userDefault dictionaryRepresentation] allKeys] containsObject:#"redWines"])
&&([[[userDefault dictionaryRepresentation] allKeys] containsObject:#"whiteWines"])
&&([[[userDefault dictionaryRepresentation] allKeys] containsObject:#"otherWines"])) {
if([userDefault objectForKey:#"redWines"] != nil && [userDefault objectForKey:#"whiteWines"] != nil && [userDefault objectForKey:#"otherWines"] != nil) {
//Try to load data from NSUserDefaults
NSData *decodedRedWines = [userDefault objectForKey:#"redWines"];
self.redWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines] mutableCopy]; //IT WILL CRASH HERE
NSData *decodedWhiteWines = [userDefault objectForKey:#"whiteWines"];
self.whiteWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedWhiteWines] mutableCopy];
NSData *decodedOtherWines = [userDefault objectForKey:#"otherWines"];
self.otherWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedOtherWines] mutableCopy];
}
} else {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://url.com/wines.json"]]; //JSON URL
NSURLResponse *response = [[NSURLResponse alloc]init];
NSError *error;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if(data != nil) { //No errors
//Passing from JSON to an NSArray
NSArray * JSONObjects = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
if (JSONObjects != nil) {
//No errors
for(NSDictionary *dict in JSONObjects){
WineModel *wine = [[WineModel alloc] initWithDictionary:dict];
if(wine.name != nil && wine.wineCompanyName != nil && wine.type != nil && wine.origin != nil ) {
if ([wine.type isEqualToString:RED_WINE_KEY]) {
if (!self.redWines) {
self.redWines = [NSMutableArray arrayWithObject:wine];
}
else {
[self.redWines addObject:wine];
}
}
else if ([wine.type isEqualToString:WHITE_WINE_KEY]) {
if (!self.whiteWines) {
self.whiteWines = [NSMutableArray arrayWithObject:wine];
}
else {
[self.whiteWines addObject:wine];
}
}
else {
if (!self.otherWines) {
self.otherWines = [NSMutableArray arrayWithObject:wine];
}
else {
[self.otherWines addObject:wine];
}
}
}
}
} else {
NSLog(#"JSON parsing error: %#", error.localizedDescription);
}
} else {
NSLog(#"Server error: %#", error.localizedDescription);
}
//Storing the array of wine objects in the NSUserDefaults
NSData *encodedRedWines = [NSKeyedArchiver archivedDataWithRootObject:_redWines];
[userDefault setObject:encodedRedWines forKey:#"redWines"];
NSData *encodedWhiteWines = [NSKeyedArchiver archivedDataWithRootObject:_whiteWines];
[userDefault setObject:encodedWhiteWines forKey:#"whiteWines"];
NSData *encodedOtherWines = [NSKeyedArchiver archivedDataWithRootObject:_otherWines];
[userDefault setObject:encodedOtherWines forKey:#"otherWines"];
}
}
return self;
}
-(WineModel *) redWineAtIndex: (NSUInteger) index {
return [self.redWines objectAtIndex:index];
}
-(WineModel *) whiteWineAtIndex: (NSUInteger) index{
return [self.whiteWines objectAtIndex:index];
}
-(WineModel *) otherWineAtIndex: (NSUInteger) index{
return [self.otherWines objectAtIndex:index];
}
#end
So, the first time you launch the app it will download the data from a JSON file that is in the web, then store the info in the NSUserDefaults. It seems like this step it's done correctly (at least doesn't crash at this point). The problem comes after launching the app the second time. It will check if there are local data store under the NSUserDefault, if so, it'll try to load the data and store into an NSMutableAtray. Unfortunately it won't do so, It crashes here self.redWines =[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines]; with the error code I wrote before. When debugging, I can see that there is data when retrieving the redWineskey, but it seems like something it's going wrong.
Mind that I'm using a customized initializer (initWithDictionary) for creating my wines object instead of the default init method. I don't know if it could be the reason of the crash...
Here's the full log:
2017-05-22 20:31:30.354640+0200 App[1905:891526] -[NSTaggedPointerString objectForKey:]: unrecognized selector sent to instance 0xa5c064950b08843b
2017-05-22 20:31:30.354932+0200 App[1905:891526] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSTaggedPointerString objectForKey:]: unrecognized selector sent to instance 0xa5c064950b08843b'
*** First throw call stack:
(0x18e0bafe0 0x18cb1c538 0x18e0c1ef4 0x18e0bef54 0x18dfbad4c 0x1000591d8 0x100057dec 0x18eb0a430 0x18eb10f10 0x18eaa684c 0x18eb0a430 0x18eb09b68 0x18eb08d94 0x100061118 0x1000621d0 0x10005c120 0x19425d204 0x194469738 0x19446f1e0 0x194483d18 0x19446c474 0x18fc63884 0x18fc636f0 0x18fc63aa0 0x18e06942c 0x18e068d9c 0x18e0669a8 0x18df96da4 0x194256384 0x194251058 0x100060b90 0x18cfa559c)
libc++abi.dylib: terminating with uncaught exception of type NSException
Any ideas??
Thanks in advance!!
There is a typo in your initWithCoder method:
self.wineCompanyName = [decoder decodeObjectForKey:#"comapny"];
If that does not fix it, I would look at the NSUserDefaults documentation more closely - it says "Values returned from NSUserDefaults are immutable, even if you set a mutable object as the value." Your redWines property is defined as an NSMutableArray.
To make an immutable object mutable just call mutableCopy
#property (strong, nonatomic) NSMutableArray *redWines;
...
self.redWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines] mutableCopy];
I'm working on a project that requires a tableView list of categorized grocery items. Each category can have n depth. The JSON response from the API looks like this.
"items":[
{
"id":"5366f8d3e4b0e44dc2d4a6fb",
"name":"String Cheese"
"description":"Sargento String Cheese",
"categorization":[
[
"Dairy",
"Cheese"
]
]
},
{
"id":"5366f8d3e4b0e44dc2d1a6fb",
"name":"Budlight 6-pk"
"description":"Budlight 12-pk",
"categorization":[
[
"Beverages",
"Alcohol",
"Beer"
]
]
}
]
Right now I'm creating Item objects from the item dictionaries and storing them in a mutable array like below.
NSArray *itemsArray = [response objectForKey:items];
NSMutableArray *itemsMutableArray = [[NSMutableArray alloc] init];
for(NSDictionary *itemDict in itemsArray){
Item *itemObj = [[Item alloc] initWithDictionary:itemDict]
[itemsMutableArray addObject:itemObj];
}
I would like to loop through itemsMutableArray and create a tree data structure that has a path from the root to each of the items. Then, I would like to be able to use the tree as a datasource for tableViews in each level of category.
Here's what my Item class header looks like.
#interface Item : NSObject
#property (nonatomic, strong) NSString *id;
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSString *description;
#property (nonatomic, strong) NSArray *categorization;
#end
...and the implementation
#import "Item.h"
#implementation Item
- (id)initWithDictionary:(NSDictionary *)objDictionary{
if (self = [super init]) {
self.id = [objDictionary valueForKey:#"id"];
self.name = [objDictionary valueForKey:#"name"];
self.description = [objDictionary valueForKey:#"description"];
self.categorization = [objDictionary valueForKey:#"categorization"];
}
return self;
}
#end
I am not very familiar with tree data structures and recursion. I would greatly appreciate any help on how to approach this. Thanks!
If you need simple node tree data structure. How about this way?
Hope this little help.
Header
#interface ItemCategory : NSObject
#property (nonatomic, strong) NSString *name;
#property (nonatomic) ItemCategory *parent;
#property (nonatomic, strong) NSMutableArray *children;
-(id)initWithName:(NSString *)n parent:(ItemCategory *)p;
#end
#interface CategoryTree : NSObject
#property (nonatomic, strong) ItemCategory *root;
-(ItemCategory *)_getChildCategory:(ItemCategory *)category name:(NSString *)name;
-(ItemCategory *)_addChildCategory:(ItemCategory *)category name:(NSString *)name;
-(void)_dumpCategory:(ItemCategory *)category depth:(int)depth;
-(void)dump;
-(ItemCategory *)getCategory:(NSArray *)arr;
-(void)addCategory:(NSArray *)arr;
#end
Source
#implementation CategoryTree
#synthesize root;
-(id)init {
if (self = [super init]) {
root = [[ItemCategory alloc] initWithName:#"root" parent:nil];
}
return self;
}
-(ItemCategory *)_getChildCategory:(ItemCategory *)category name:(NSString *)name {
for (ItemCategory *child in category.children)
if ([child.name isEqualToString:name])
return child;
return nil;
}
-(ItemCategory *)_addChildCategory:(ItemCategory *)category name:(NSString *)name {
ItemCategory *child = [self _getChildCategory:category name:name];
if (child)
return child;
child = [[ItemCategory alloc] initWithName:name parent:category];
[category.children addObject:child];
return child;
}
-(void)_dumpCategory:(ItemCategory *)category depth:(int)depth{
NSString *parentStr = #"";
ItemCategory *parent = category.parent;
while (parent) {
parentStr = [NSString stringWithFormat:#"%#%#%#", parent.name, parentStr.length > 0 ? #">" : #"", parentStr];
parent = parent.parent;
}
NSLog(#"%#%#%#", parentStr, parentStr.length > 0 ? #">" : #"", category.name);
for (ItemCategory *child in category.children) {
[self _dumpCategory:child depth:depth + 1];
}
}
-(void)dump {
[self _dumpCategory:root depth:0];
}
-(ItemCategory *)getCategory:(NSArray *)arr {
ItemCategory *category = root;
for (NSString *categoryName in arr) {
category = [self _getChildCategory:category name:categoryName];
if (!category)
return nil;
}
return category;
}
-(void)addCategory:(NSArray *)arr {
if ([self getCategory:arr])
return;
ItemCategory *category = root;
for (NSString *categoryName in arr) {
ItemCategory *childCategory = [self _getChildCategory:category name:categoryName];
if (!childCategory) {
childCategory = [self _addChildCategory:category name:categoryName];
}
category = childCategory;
}
}
#end
Usage
CategoryTree *tree = [[CategoryTree alloc] init];
[tree addCategory:#[#"Dairy", #"Cheese"]];
[tree addCategory:#[#"Dairy", #"Milk"]];
[tree addCategory:#[#"Beverages", #"Alcohol", #"Beer"]];
[tree addCategory:#[#"Beverages", #"Alcohol", #"Wine"]];
[tree addCategory:#[#"Beverages", #"Non-Alcohol", #"Cola"]];
[tree dump];
Result
root
root>Dairy
root>Dairy>Cheese
root>Dairy>Milk
root>Beverages
root>Beverages>Alcohol
root>Beverages>Alcohol>Beer
root>Beverages>Alcohol>Wine
root>Beverages>Non-Alcohol
root>Beverages>Non-Alcohol>Cola
well I have found a way to implement what you need. I do not know how optimised it is since i do not how many items you'll be receiving . The implementation is given below.
You need to start with adding this dictionary in Item.h #property (nonatomic, strong) NSMutableDictionary *catTree;
Next do this to get the tree
[itemsMutableArray enumerateObjectsUsingBlock:^(Item *itm, NSUInteger i,BOOL *stop){
itm.catTree = [NSMutableDictionary dictionary];
NSString *dairy = #"",*beverage = #"";
for (NSArray *catArray in itm.categorization) {
/*
Everything below is written assuming the format of the JSON will be "as-is"
*/
if ([catArray containsObject:#"Dairy"]) {
//Take everything except Dairy
NSArray *stripedArray = [catArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"SELF != \"Dairy\""]];
int i = 0;
//Loop through the array to get any sub categories.
while (i < stripedArray.count) {
dairy = [dairy stringByAppendingString:[NSString stringWithFormat:(i == stripedArray.count-1)?#"%# ":#"%#->",stripedArray[i]]]; //Space at the end to account for similar entry in the same category for e.g two dairy products.
i++;
}
} else if ([catArray containsObject:#"Beverages"]) {
NSArray *stripedArray = [catArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"SELF != \"Beverages\""]];
int i = 0;
while (i < stripedArray.count) {
beverage = [beverage stringByAppendingString:[NSString stringWithFormat:(i == stripedArray.count-1)?#"%# ":#"%#->",stripedArray[i]]];
i++;
}
}
}
//Set the category tree for every item using a dictionary
[itm.catTree setValue:dairy forKey:#"Dairy"];
[itm.catTree setValue:beverage forKey:#"Beverage"];
NSLog(#"%#",itm.catTree);
}];
the above code gives the following output for your json
{
Beverage = "";
Dairy = "Cheese ";
}
{
Beverage = "Alcohol->Beer ";
Dairy = "";
}
For multiple beverages
{
Beverage = "Alcohol->Beer Alcohol->Wine->Red Soda->Coke ";
Dairy = "";
}
Hope this helps.
Im trying to make user role page that get data from login. I created the own delegate function to call the webservice but app getting crash due to-[__NSArrayM setRoleHistorys:]: unrecognized selector sent to instance.
Here is my code: in .m file :
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
nodeContent = [[NSMutableString alloc]init];
}
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
arrayitems = [[NSMutableArray alloc] init];
U serRoleDataParser *userroleParser = [[UserRoleDataParser alloc] init];
// UserRole *currentStudent = (UserRole *) arrayitems;
NSString *Username = username.text;
NSLog(#"the String value%#",Username);
[userroleParser getUserHistoryForIndex:0 LoginId:username.text];
NSLog(#"the String user value %#",username.text);
userroleParser.delegate = self;
}
- (void) didrecieveData : (NSArray *) userHistories forIndex :(int) index
{
arrayitems = [[NSMutableArray alloc] init];
UserRole *roles = (UserRole *) arrayitems;
roles.RoleHistorys = userHistories;
datadisplay.text = roles.role;
NSLog(#"the Success data%#", datadisplay.text);
}
in delegate file .h
#interface UserRole : NSObject
#property (nonatomic,copy) NSString *username;
#property (nonatomic,copy) NSString *role;
#property (nonatomic,copy) NSString *empcode;
#property (nonatomic,copy)NSMutableArray * RoleHistorys;
#end
Dataparser.h file (delegate)
#import <Foundation/Foundation.h>
#import "UserRole.h"
#protocol UserRoleDataParserDelegate <NSObject>
- (void) didrecieveData : (NSArray *) userHistories forIndex :(int) index ;
#end
#interface UserRoleDataParser : NSObject<NSXMLParserDelegate>
{
NSMutableData *xmlData;
NSXMLParser *userroleParser;
NSMutableString *capturedString;
BOOL captureCharacters;
NSMutableArray *userHistories;
}
- (void) getUserHistoryForIndex : (int) index LoginId :(NSString*) loginId;
#property (weak,nonatomic) id <UserRoleDataParserDelegate> delegate;
#property (nonatomic) int index;
#end
am getting output in nslog but the app getting crash.
This code isn't correct:
arrayitems = [[NSMutableArray alloc] init];
UserRole *roles = (UserRole *) arrayitems;
You can't just cast an array to your custom class type (unless you have subclassed NSMutableArray and the instance is actually of the subclass type). You need to create an instance of or find the correct instance you want before you try to use it.
You create NSMutableArray and you cast it to UserRole object, after that you try to save data in that: roles.RoleHistorys :
arrayitems = [[NSMutableArray alloc] init];
UserRole *roles = (UserRole *) arrayitems;
roles.RoleHistorys = userHistories; //<- Error happened here
So you try to save data by calling selector setRoleHistorys on object of type NSMutableArray which you cannot do because NSMutableArray doesn't contain that property.
My application is designed to view the catalog with products. All data received from the server (xml) are parsed in NSDictionary. So NSDictionary contains about 5000 items. Reading a dictionary using NSKeyedUnarchiver takes 24 seconds. It is unacceptable.
I don't need forward and backward compatible, because after app updating catalog will be downloaded again and old data will be deleted.
My dictionary isn't property list, so writeToFile isn't working.
NSArchiever(NSUnarchiever) would be a great solution to the problem, but it replaced by NSKeyedArchiver.
Any ideas? Maybe I should use CoreData but I don't know anything about it.
Thanks
Have you tried saving it as a JSON formatted file? NSJSONSerialization would help you go back and forth between file data and a NSDictionary. It was added in iOS5.
https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html
Let me know if it performs well enough for you.
My solution is to use custom serialization. In the end, all data is presented as property list. All my custom classes that should be serialized support protocol PropertyListCoder (like NSCoder):
#protocol PropertyListCoder
- (id)encodeToPropertyListObject;
- (id)initWithPropertyListObject:(id)object;
#end
For example, serialization for class Product:
#interface Product : NamedObject <NSCoding, PropertyListCoder> {
}
#property (nonatomic, readwrite) uint mId;
#property (nonatomic, retain) NSString *mDesc;
#property (nonatomic, retain) NSString *mIcon;
#property (nonatomic, retain) NSString *mCode;
#property (nonatomic, readwrite) uint mSort;
#property (nonatomic, retain) NSMutableArray *mArrOfText;
#property (nonatomic, readonly) NSMutableArray *mProperties;
#property (nonatomic, retain) NSString *mImage;
#property (nonatomic, readwrite) float mPrice;
#property (nonatomic, readwrite) float mDiscPrice;
#end
And here are methods of the protocol:
- (id)encodeToPropertyListObject {
id superPropList = [super encodeToPropertyListObject];
NSNumber* idNumber = [[NSNumber alloc] initWithInt:mId];
NSNumber* sortNumber = [[NSNumber alloc] initWithInt:mSort];
NSMutableArray* arrOfTextPropList = [[NSMutableArray alloc] init];
for (NSAttString* str in self.mArrOfText) {
[arrOfTextPropList addObject:[str encodeToPropertyListObject]];
}
NSMutableArray* propPropList = [[NSMutableArray alloc] init];
for (Property* prop in self.mProperties) {
[propPropList addObject:[prop encodeToPropertyListObject]];
}
NSNumber* priceNumber = [[NSNumber alloc] initWithInt:mPrice];
NSNumber* discPriceNumber = [[NSNumber alloc] initWithInt:mDiscPrice];
NSArray* res = [NSArray arrayWithObjects:superPropList, idNumber, [Utility notNilStringWithString:mDesc], [Utility notNilStringWithString:mIcon], [Utility notNilStringWithString:mCode], sortNumber, arrOfTextPropList, propPropList, [Utility notNilStringWithString:mImage], priceNumber, discPriceNumber, nil];
[idNumber release];
[sortNumber release];
[arrOfTextPropList release];
[propPropList release];
[priceNumber release];
[discPriceNumber release];
return res;
}
- (id)initWithPropertyListObject:(id)object {
NSArray* arr = (NSArray*)object;
self = [super initWithPropertyListObject:[arr objectAtIndex:0]];
if (self) {
mId = [[arr objectAtIndex:1] intValue];
mDesc = [[arr objectAtIndex:2] retain];
mIcon = [[arr objectAtIndex:3] retain];
mCode = [[arr objectAtIndex:4] retain];
mSort = [[arr objectAtIndex:5] intValue];
mArrOfText = [[NSMutableArray alloc] init];
mProperties = [[NSMutableArray alloc] init];
for (id subObj in (NSArray*)[arr objectAtIndex:6]) {
NSAttString* str = [[NSAttString alloc] initWithPropertyListObject:subObj];
[mArrOfText addObject:str];
[str release];
}
for (id subObj in (NSArray*)[arr objectAtIndex:7]) {
Property* prop = [[Property alloc] initWithPropertyListObject:subObj];
[mProperties addObject:prop];
[prop release];
}
mImage = [[arr objectAtIndex:8] retain];
mPrice = [[arr objectAtIndex:9] floatValue];
mDiscPrice = [[arr objectAtIndex:10] floatValue];
}
return self;
}
Root object could be NSDictionary or NSArray. Then for read / write I use NSPropertyListSerialization with binary format.
And now reading the dictionary takes 3.5 seconds!