Save large NSDictionary to disk iOS - ios

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!

Related

retrieve RLMObject where value exist in RLMArray<RLMString*> in iOS

i create Stock_RLM : RLMObject and Stock_RLM have RLMArray of RLMString.
i want filer RLMObject using RLMArray's Value. It's Possible?
#import <Realm/Realm.h>
#interface Stock_RLM : RLMObject
#property NSString *_id;
#property NSString *code;
#property NSString *name;
#property NSString *full_name;
#property NSString *type;
#property RLMArray<RLMString> *stock_sectors;
#end
RLM_ARRAY_TYPE(Stock_RLM)
My RLMObject is : -
Stock_RLM {
_id = 5b60554726f2fe334cad881d;
code = GRASIM;
name = GRASIM;
full_name = Grasim Industries Limited;
type = NSE;
stock_sectors = RLMArray<string> <0x1c4114d00> (
[0] 5b6046d1f1e8972b9cebd584,
[1] 5b6046d1f1e8972b9cebd586,
[2] 5b6046d1f1e8972b9cebd587,
[3] 5b6046d1f1e8972b9cebd588,
[4] 5b6046d1f1e8972b9cebd59a,
[5] 5b6046d1f1e8972b9cebd5c1
);
}
i Want to filter '5b6046d1f1e8972b9cebd584'exist in RLMArray in RLMObject or Not.
in your **.h
#interface TESTClass:NSObject
#property NSString *_id;
#property NSString *_code;
#property NSString *_name;
#property NSArray *theStrArr;
#end
in your **.m
#implementation TESTClass
#end
in somewhere you test
TESTClass *obje1 = [[TESTClass alloc] init];
TESTClass *obje2 = [[TESTClass alloc] init];
TESTClass *obje3 = [[TESTClass alloc] init];
obje1.theStrArr = [NSArray arrayWithObjects:#"11",#"22",#"33",nil];
obje2.theStrArr = [NSArray arrayWithObjects:#"44",#"55",#"66",nil];
obje3.theStrArr = [NSArray arrayWithObjects:#"77",#"88",#"99",nil];
NSArray *totalArr = [NSArray arrayWithObjects:obje1,obje2,obje3, nil];
NSString *testStr = [NSString stringWithFormat:#"22"];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"theStrArr CONTAINS %#",testStr];
NSArray *resultArr = [totalArr filteredArrayUsingPredicate:pred];
CLog(#"the result arr %#",resultArr);

Custom Class sort property with NSDate not working in Objective-C

I create the custom class name with FileModel.
FileModel.h
#import <Foundation/Foundation.h>
#interface FileModel : NSObject
#property (nonatomic, copy) NSString *fileName;
#property (nonatomic, copy) NSString *fileType;
#property (nonatomic, strong) NSDate *editDate;
#property (nonatomic, assign) NSInteger fileSize;
#end
I want to sort the FileModel with the editDate, but it is not work.
I create the sample like below .m
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *fileSampleName = [[NSArray alloc] initWithObjects:#"apple.png",#"banana.png",#"cherry.png",#"durian.png",#"grape.png",#"avocado.png", nil];
NSMutableArray *fileData = [NSMutableArray new];
FileModel *fileModel = nil;
for( NSInteger i = 0 ; i < fileSampleName.count ; i++){
fileModel = [FileModel new];
fileModel.fileName = [fileSampleName objectAtIndex:i];
fileModel.fileType = #"photo";
fileModel.fileSize = 0;
fileModel.editDate = [NSDate new];
[fileData addObject:fileModel];
// test for nsdate interveral
[NSThread sleepForTimeInterval:1];
}
NSArray *sortedArray;
sortedArray = [fileData sortedArrayUsingComparator:^NSComparisonResult(FileModel *a, FileModel *b) {
NSDate *first = [(FileModel*)a editDate];
NSDate *second = [(FileModel*)a editDate];
return [second compare:first];
}];
NSLog(#"sortedArray:%#",sortedArray);
// log
for (FileModel *fm in sortedArray) {
NSLog(#"sortedArray:%#", fm.fileName);
}
}
Why I NSLog sorted order is not
avocado.png -> grape.png -> durian.png -> cherry.png -> banana.png ->
apple.png
Thank you very much.
You miss typed a again instead of b in second variable
NSArray *sortedArray;
sortedArray = [fileData sortedArrayUsingComparator:^NSComparisonResult(FileModel *a, FileModel *b) {
NSDate *first = [(FileModel*)a editDate];
NSDate *second = [(FileModel*)b editDate];
return [second compare: first];
}];
Try this
NSArray *sortArray = [fileData sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
NSDate *first = [(FileModal*)obj1 editDate];
NSDate *second = [(FileModal*)obj2 editDate];
return !([first compare:second] == NSOrderedDescending);
}];

Multiple NSObject Initialization Causes code to stop executing

So I have a custom TVShow object that has some base fields like id, showName, airDate etc. which are all either NSStrings or NSIntegers and I am attempting to create a bunch of these objects via some data I have gotten from an API online.
So I loop through my NSArray of JSON data and create a TVShow object for each response:
TVShow *show = [[TVShow alloc] initWithData:[NSJSONSerialization JSONObjectWithData:data options:0 error:&error]];
[self.showArray addObject:show];
However, only 7 of these ever get created and then any code below this just ceases to run. I have a NSLog(#"Added"); printing after I create the show and it only gets called 6 times. If I add breakpoints after any of this code, they never get called. I'm not sure what's going on but it must be something to do with how I have set up my TVShow object?
It currently looks like:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#interface TVShow : NSObject
#property NSInteger showID;
#property NSString *showName;
#property NSString *airDate;
#property double rating;
#property NSString *imageUrl;
#property NSString *showSummary;
#property NSString *episodeSummary;
#property NSInteger season;
#property NSInteger episode;
- (id)initWithData:(NSDictionary *)data;
#end
and the .m file:
#import <UIKit/UIKit.h>
#import "TVShow.h"
#implementation TVShow
- (id)initWithData:(NSDictionary*)data {
self = [super init];
if(self) {
[self buildObjectFromData:data];
}
return self;
}
-(void)buildObjectFromData:(NSDictionary*)data {
NSDictionary *dict = [data objectForKey:#"_embedded"];
NSDictionary *dict2 = [dict objectForKey:#"nextepisode"];
NSDictionary *dict3 = [data objectForKey:#"image"];
NSString *airDate = [dict2 valueForKey:#"airstamp"];
NSInteger season = [[dict2 valueForKey:#"season"] integerValue];
NSInteger episode = [[dict2 valueForKey:#"episode"] integerValue];
NSString *episodeSummary = [dict2 valueForKey:#"summary"];
NSString *showName = [data valueForKey:#"name"];
NSString *showSummary = [data valueForKey:#"summary"];
NSString *imageUrl = [dict3 valueForKey:#"medium"];
NSInteger showID = [[data valueForKey:#"id"] integerValue];
self.airDate = airDate;
self.showName = showName;
self.season = season;
self.episode = episode;
self.showSummary = [self stringByStrippingHTML:showSummary];
self.episodeSummary = [self stringByStrippingHTML:episodeSummary];
self.imageUrl = imageUrl;
self.showID = showID;
}
-(NSString *) stringByStrippingHTML:(NSString*)string {
NSRange r;
NSString *s = string;
while ((r = [s rangeOfString:#"<[^>]+>" options:NSRegularExpressionSearch]).location != NSNotFound)
s = [s stringByReplacingCharactersInRange:r withString:#""];
return s;
}
#end
If I create the object as just: [[TVShow alloc] init]; everything works fine, so it must be something wrong with this model is what I'm thinking. I'm unsure of what to try next, but any help would be greatly appreciated here.
Turns out there were cases that casued:
-(NSString *) stringByStrippingHTML:(NSString*)string {
NSRange r;
NSString *s = string;
while ((r = [s rangeOfString:#"<[^>]+>" options:NSRegularExpressionSearch]).location != NSNotFound)
s = [s stringByReplacingCharactersInRange:r withString:#""];
return s;
}
to go into infinite loops. Removing this snippet fixed the freeze.

NSUserdefaults not working with NSKeyedArchiver

I have a NSMutableArray filled with objects of my Movie class wich i want to save but it doesn't work and i can not figure out why...
Movie.h:
#interface Movie : NSObject <NSCoding>{
NSString *name;
int year;
int length;
NSString *file_size;
int rating;
NSArray *genre;
NSString *plot;
}
#property (nonatomic, retain) NSString *name;
#property (nonatomic, assign) int year;
#property (nonatomic, assign) int length;
#property (nonatomic, retain, retain) NSString *file_size;
#property (nonatomic, assign) int rating;
#property (nonatomic, retain) NSArray *genre;
#property (nonatomic, retain) NSString *plot;
-(id) initWithName:(NSString*)newName year:(int)newYear length:(int)newLength filesize:(NSString*)newFileSize rating:(int)newRating genre:(NSArray*)newGenre plot:(NSString*)newPlot;
- (void) encodeWithCoder : (NSCoder *)encode ;
- (id) initWithCoder : (NSCoder *)decode;
#end
Movie.m:
#implementation Movie
#synthesize name;
#synthesize year;
#synthesize length;
#synthesize file_size;
#synthesize rating;
#synthesize genre;
#synthesize plot;
-(id)initWithName:(NSString *)newName year:(int)newYear length:(int)newLength filesize:(NSString *)newFileSize rating:(int)newRating genre:(NSArray *)newGenre plot:(NSString *)newPlot{
self.name = newName;
self.year = newYear;
self.length = newLength;
self.file_size = newFileSize;
self.rating = newRating;
self.genre = newGenre;
self.plot = newPlot;
return self;
}
- (void)encodeWithCoder:(NSCoder *)encode;
{
[encode encodeObject:name forKey:#"name"];
[encode encodeInt32:year forKey:#"year"];
[encode encodeInt32:length forKey:#"length"];
[encode encodeObject:file_size forKey:#"file_size"];
[encode encodeInt32:rating forKey:#"rating"];
[encode encodeObject:genre forKey:#"genre"];
[encode encodeObject:plot forKey:#"plot"];
}
- (id)initWithCoder:(NSCoder *)decode;
{
NSString *name_decode = [decode decodeObjectForKey:#"name"];
int year_decode = [decode decodeInt32ForKey:#"year"];
int length_decode = [decode decodeInt32ForKey:#"length"];
NSString *file_size_decode = [decode decodeObjectForKey:#"file_size"];
int rating_decode = [decode decodeInt32ForKey:#"rating"];
NSArray *genre_decode = [decode decodeObjectForKey:#"genre"];
NSString *plot_decode =[decode decodeObjectForKey:#"plot"];
return [self initWithName:name_decode year:year_decode length:length_decode filesize:file_size_decode rating:rating_decode genre:genre_decode plot:plot_decode];
}
#end
Save Action (Movies is the NSMutableArray containing my Objects):
NSUserDefaults *userDefault=[NSUserDefaults standardUserDefaults];
NSData *encodedData = [NSKeyedArchiver archivedDataWithRootObject:Movies];
[userDefault setObject:encodedData forKey:[NSString stringWithFormat:#"MOVIES"]];
Load Action:
NSData *decodedData = [userDefault objectForKey: [NSString stringWithFormat:#"MOVIES"]];
NSArray *decodedArray =[NSKeyedUnarchiver unarchiveObjectWithData: decodedData];
The returned Array is always (null)... i have no clue
I tried several different kind of code snippets i found on the internet and/or stackoverflow
Your Movie initWithName... method is incorrect. It needs to be:
- (instancetype)initWithName:(NSString *)newName year:(int)newYear length:(int)newLength filesize:(NSString *)newFileSize rating:(int)newRating genre:(NSArray *)newGenre plot:(NSString *)newPlot {
self = [super init];
if (self) {
self.name = newName;
self.year = newYear;
self.length = newLength;
self.file_size = newFileSize;
self.rating = newRating;
self.genre = newGenre;
self.plot = newPlot;
}
return self;
}
Also, you seem to be following a very out-of-date tutorial.
You don't need to declare the ivars for your properties.
You don't need the calls to #synthesize.
You should be using ARC instead of MRC, Therefore your retain properties should be strong (thought the NSString properties should be copy.
Your init methods should return instancetype, not id.
With all of that in mind, your Movie class should be as follows:
Movie.h
#interface Movie : NSObject <NSCoding>
#property (nonatomic, copy) NSString *name;
#property (nonatomic, assign) int year;
#property (nonatomic, assign) int length;
#property (nonatomic, copy) NSString *file_size;
#property (nonatomic, assign) int rating;
#property (nonatomic, strong) NSArray *genre;
#property (nonatomic, copy) NSString *plot;
- (instancetype)initWithName:(NSString *)newName year:(int)newYear length:(int)newLength filesize:(NSString *)newFileSize rating:(int)newRating genre:(NSArray *)newGenre plot:(NSString *)newPlot;
#end
Movie.m
#implementation Movie
- (instancetype)initWithName:(NSString *)newName year:(int)newYear length:(int)newLength filesize:(NSString *)newFileSize rating:(int)newRating genre:(NSArray *)newGenre plot:(NSString *)newPlot {
self = [super init];
if (self) {
_name = newName;
_year = newYear;
_length = newLength;
_file_size = newFileSize;
_rating = newRating;
_genre = newGenre;
_plot = newPlot;
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encode;
{
[encode encodeObject:self.name forKey:#"name"];
[encode encodeInt32:self.year forKey:#"year"];
[encode encodeInt32:self.length forKey:#"length"];
[encode encodeObject:self.file_size forKey:#"file_size"];
[encode encodeInt32:self.rating forKey:#"rating"];
[encode encodeObject:self.genre forKey:#"genre"];
[encode encodeObject:self.plot forKey:#"plot"];
}
- (instancetype)initWithCoder:(NSCoder *)decode;
{
NSString *name_decode = [decode decodeObjectForKey:#"name"];
int year_decode = [decode decodeInt32ForKey:#"year"];
int length_decode = [decode decodeInt32ForKey:#"length"];
NSString *file_size_decode = [decode decodeObjectForKey:#"file_size"];
int rating_decode = [decode decodeInt32ForKey:#"rating"];
NSArray *genre_decode = [decode decodeObjectForKey:#"genre"];
NSString *plot_decode =[decode decodeObjectForKey:#"plot"];
return [self initWithName:name_decode year:year_decode length:length_decode filesize:file_size_decode rating:rating_decode genre:genre_decode plot:plot_decode];
}
#end
You don't show how you create and populate your Movies variable (which should be named movies, not Movies. Make sure it isn't nil.
Also, don't needlessly use stringWithFormat.
Your saving code should be:
NSUserDefaults *userDefault=[NSUserDefaults standardUserDefaults];
NSData *encodedData = [NSKeyedArchiver archivedDataWithRootObject:movies];
[userDefault setObject:encodedData forKey:#"MOVIES"];
and your loading code should be:
NSData *decodedData = [userDefault objectForKey:#"MOVIES"];
NSArray *decodedArray = [NSKeyedUnarchiver unarchiveObjectWithData: decodedData];
A quick test is to see if the mutable array is actually not nil itself. Try outputting the mutable array before setting it in userDefaults.
Make sure the mutable array is initialized before trying to add objects to it.
movies = [[NSMutableArray alloc] init];

object is not saved in array

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)

Resources