Possible to index store with Core Data when using the NSInMemoryStoreType? - objective-c

I have an NSArray of a few thousand in-memory NSDictionary instances (containing strings and numbers) against which I need to perform arbitrary queries at runtime. Using filteredArrayUsingPredicate winds up yielding unacceptable performance. I could manually build up indices on each field and access those dictionaries, but I figured it might be simpler to just build up a dynamic in-memory Core Data model with indexed attributes, convert the NSDictionary instances into NSManagedObjects, and then perform the queries with NSFetchRequests.
Unfortunately, the NSInMemoryStoreType model doesn't seem to respect the "indexed" property of the NSAttributeDescription: queries against the Core Data model are taking about 50% longer than just doing the old filteredArrayUsingPredicate on the array of dictionaries. Is there some trick to getting a NSInMemoryStoreType model to create in-memory indices, or does is the attribute simply ignored? Using a SQLite store is not an option for this application, since the types of the attributes change frequently.
Here's the code I'm using to compare the performance of the two different searching mechanisms:
- (void)testInMemoryCoreDataEfficienctQuery {
static const NSInteger InstanceCount = 5000; // the number of instances to test
static NSString *EntityName = #"EntityPerformanceTest";
static NSString *AttributeName = #"attrName";
static NSString *PredicateVariable = #"predicateVariable";
NSError *error = nil;
NSManagedObjectContext *moc;
NSEntityDescription *entity;
{
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] init];
{
NSMutableArray *entities = [NSMutableArray array];
entity = [[NSEntityDescription alloc] init];
entity.name = EntityName;
NSMutableArray *attrs = [NSMutableArray array];
{
NSAttributeDescription *attr = [[NSAttributeDescription alloc] init];
attr.name = AttributeName;
attr.attributeType = NSStringAttributeType;
attr.indexed = YES; // ideally this would speed up searches on strings
[attrs addObject:attr];
}
entity.properties = attrs;
[entities addObject:entity];
mom.entities = entities;
}
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
NSPersistentStore *ps = [psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:&error];
// NSPersistentStore *ps = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:[[NSTemporaryDirectory() stringByAppendingPathComponent:[NSString randomUUID]] stringByAppendingPathExtension:#"sqlite"]] options:nil error:&error];
STAssertNotNil(ps, nil);
STAssertNil(error, #"%#", error);
moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
moc.persistentStoreCoordinator = psc;
}
[moc processPendingChanges];
[moc save:&error];
[moc reset];
STAssertNil(error, #"%#", error);
// now test searching in a MOC vs. in a collection of dictionaries
NSMutableArray *strings = [NSMutableArray array];
NSMutableArray *dicts = [NSMutableArray arrayWithCapacity:InstanceCount];
{
for (int i = 0; i < InstanceCount; i++) {
// create an arbitrary random string we will store and later query against
CFUUIDRef randomUUID = CFUUIDCreate(NULL);
NSString *uuidString = (NSString *)CFBridgingRelease(CFUUIDCreateString(NULL, randomUUID));
CFRelease(randomUUID);
[strings addObject:uuidString];
// create the dictionary
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:uuidString forKey:AttributeName];
[dicts addObject:dict];
// create the managed instance
NSManagedObject *ob = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:moc];
[ob setValue:uuidString forKey:AttributeName];
}
}
[moc processPendingChanges];
STAssertEquals([strings count], [[NSSet setWithArray:strings] count], #"strings were not unique");
NSPredicate *query = [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:AttributeName] rightExpression:[NSExpression expressionForVariable:PredicateVariable] modifier:(NSDirectPredicateModifier) type:(NSEqualToPredicateOperatorType) options:(0)];
for (int iter = 0; iter < 2; iter++) {
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:EntityName];
[fetch setFetchLimit:1];
[fetch setFetchBatchSize:1];
// time searching with Core Data
CFAbsoluteTime mocStart = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < InstanceCount; i++) {
fetch.predicate = [query predicateWithSubstitutionVariables:[NSDictionary dictionaryWithObject:[strings objectAtIndex:arc4random() % strings.count] forKey:PredicateVariable]];
NSArray *results = [moc executeFetchRequest:fetch error:&error];
NSParameterAssert(!error);
NSParameterAssert(results.count == 1);
}
CFAbsoluteTime mocEnd = CFAbsoluteTimeGetCurrent();
// time searching with dictionaries
CFAbsoluteTime dictStart = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < InstanceCount; i++) {
NSArray *results = [dicts filteredArrayUsingPredicate:[query predicateWithSubstitutionVariables:[NSDictionary dictionaryWithObject:[strings objectAtIndex:arc4random() % strings.count] forKey:PredicateVariable]]];
NSParameterAssert(results.count == 1);
}
CFAbsoluteTime dictEnd = CFAbsoluteTimeGetCurrent();
NSLog(#"assessed %d queries: moc=%.3f dict=%.3f", InstanceCount, mocEnd - mocStart, dictEnd - dictStart);
/*
Core Data seems to be slower, as per these results:
2012-01-10 21:19:04.247 Glimpse[9151:15503] assessed 5000 queries: moc=19.085 dict=12.186
2012-01-10 21:19:35.412 Glimpse[9151:15503] assessed 5000 queries: moc=19.001 dict=12.164
*/
}
}

Related

create a dictionary with an array of dictionaries

Noob here. I recently started working with objective C, and currently I am stuck with dictionary concept. I want to create a json object as shown below:
{"UserData": {
"Name": Mike Smith,
"Age": 32,
"category": [1,2,3],
"Weekly Data": [
{"Monday" : [1.0,2.0,3.0]},
{"Tuesday": [1.0,2.0,3.0]}
]
}
}
I wrote the following piece of code which doesn't give the desired result. I wonder if someone could help me.
-(NSString*)populateUserPreferences
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
NSMutableArray *categorydata = [[NSMutableArray alloc] init];
NSMutableArray *weeklydata = [[NSMutableArray alloc] init];
for (int i=0;i<4; i++)
{
[categorydata addObject:[NSNumber numberWithInt:i]];
}
NSMutableArray *mondaydata = [[NSMutableArray alloc] init];
for (int j=0; j<3; j++)
{
[mondaydata addObject:[NSNumber numberWithInt:j]];
}
NSMutableArray *tuesdaydata = [[NSMutableArray alloc] init];
for (int j=0; j<3; j++)
{
[tuesdaydata addObject:[NSNumber numberWithInt:j]];
}
NSDictionary *monday = [NSDictionary dictionaryWithObject:mondaydata];
NSDictionary *tuesday = [NSDictionary dictionaryWithObject:tuesdaydata];
[weeklydata addObject: monday ];
[weeklydata addObject: tuesday ];
}
[dict setObject:[NSString stringWithFormat:"Mike Smith"] forKey:#"Name"];
[dict setObject:[NSNumber numberWithInteger:32.0] forKey:#"Age"];
[dict setObject:categorydata forKey:#"category"];
[dict setObject:weeklydata forKey:#"Weekly Data"];
NSString * userdata = [dict JSONRepresentation];
NSLog(request);
NSDictionary *userdataJson = [NSDictionary dictionaryWithObject:dict forKey:#"userData"];
return [userdataJson JSONRepresentation];
}
Thanks in advance for looking into it.
Apoorva
The mistake is when creating the monday and tuesday dictionary.
// mondaydata & tuesday is just array.
NSDictionary *monday = [NSDictionary dictionaryWithObject:mondaydata];
NSDictionary *tuesday = [NSDictionary dictionaryWithObject:tuesdaydata];
This code is mistake since you did not assign the dictionary properly (where is the key for the dictionary?). Instead you should do:
NSDictionary *mondayDict = [[NSDictionary alloc] init];
[mondayDict setObject:mondaydata forKey:"Monday"];
NSDictionary *tuesdayDict = [[NSDictionary alloc] init];
[tuesdayDict setObject:tuesdaydata forKey:"Tuesday"];
Then you can add mondayDict and tuesdayDict to your array weeklydata.
ps. just a note, name your variable meaningfully. For example, mondaydata is not descriptive enough. You should use mondayArr for example. To easily identify it is an array. Just a normal coding practice to share.
NSDictionary * dict = #{#"UserData": #{
#"Name": #"Mike Smith",
#"Age": #32,
#"category": #[#1,#2,#3],
#"Weekly Data": #[
#{#"Monday" : #[#1.0,#2.0,#3.0]},
#{#"Tuesday": #[#1.0,#2.0,#3.0]}
]
}
};
NSError * error = nil;
NSData * data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error];
if (error) {
NSLog(#"%#", [error localizedDescription]);
} else {
// Do what you want
}

Objective C NSDictionary JSON in One line

i'm working on one objective C application where i'm taking JSON data and i need to insert this data(date) inside Dictionary fillDefaultColors. My fillDefaultColors should be in format like this:
self.fillDefaultColors = #{ #"2017/06/18":greenColor,
#"2017/06/19":orangeColor,
#"2017/06/20":greenColor,
...
};
but when i print in console log they are each in separate row and in application i can see colour just for last item from JSON
2017-06-19 15:30:12.310 CalendarTest[1905:364525] {
"2017/06/20" = "greenColor";
}
2017-06-19 15:30:12.311 CalendarTest[1905:364525] {
"2017/06/18" = "orangeColor";
}
So in application i see background for last date in console 2017/06/18
Here is my code
NSError *error = nil;
NSURL *url = [NSURL URLWithString: #"http://..."];
NSData *data = [NSData dataWithContentsOfURL:url options:NSDataReadingUncached error:&error];
if(!error)
{
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
NSMutableArray *array= [json objectForKey:#"horses"];
for(int i=0; i< array.count; i++)
{
NSDictionary *horsedata = [array objectAtIndex:i];
NSString *date = [horsedata objectForKey:#"date"];
NSNumber *averagetemp = [horsedata objectForKey:#"averagetemperature"];
if([averagetemp isEqual:#(28)]) {tempColor = greenColor;} else {
tempColor = orangeColor;
}
self.fillDefaultColors = #{date: tempColor};
NSLog(#"%#", _fillDefaultColors);
}
}
JSON: {"horses":[{"id":1,"name":"Horse","date":"2017/06/17","averagetemperature":28},{"id":1,"name":"Horse","date":"2017/06/18","averagetemperature":25}]}
Thanks
it s because you are allocating a new dictionary in each iteration:
self.fillDefaultColors = #{date: tempColor};
you need to append instead:
NSMutableArray *array= [json objectForKey:#"horses"];
self.fillDefaultColors = [[NSMutableDictionary alloc]init];
for(int i=0; i< array.count; i++)
{
NSDictionary *horsedata = [array objectAtIndex:i];
NSString *date = [horsedata objectForKey:#"date"];
NSNumber *averagetemp = [horsedata objectForKey:#"averagetemperature"];
if([averagetemp isEqual:#(28)]) {tempColor = greenColor;} else {
tempColor = orangeColor;
}
[self.fillDefaultColors setObject:tempColor forKey:date];
NSLog(#"%#", _fillDefaultColors);
}
This is the same solution as in Hussein's answer but with Modern Objective-C Syntax – which has been introduced at least 5 years ago.
NSArray *horses = json[#"horses"];
self.fillDefaultColors = [[NSMutableDictionary alloc] init];
for (NSDictionary *horsedata in horses)
{
NSString *date = horsedata[#"date"];
NSNumber *averagetemp = horsedata[#"averagetemperature"];
self.fillDefaultColors[date] = (averagetemp.integerValue == 28) ? greenColor : orangeColor;
NSLog(#"%#", _fillDefaultColors);
}

Reading from SQL database into NSArray

I have an iPad that reads data from an SQL database. The following code works fine and retrieves 2 fields from each record and reads them into an NSArray.
I now need to read 5 of the fields and I can't help but think that there is a better way of doing it rather than running 5 separate queries through php (the getinfo.php file with the choice parameter set to pick the different fields).
Any pointers to a better method for doing this?
NSString *strURLClass = [NSString stringWithFormat:#"%#%#", #"http://wwwaddress/getinfo.php?choice=1&schoolname=",obsSchoolName];
NSArray *observationsArrayClass = [[NSMutableArray alloc] initWithContentsOfURL:[NSURL URLWithString:strURLClass]];
observationListFromSQL = [[NSMutableArray alloc]init];
NSEnumerator *enumForObsClass = [observationsArrayClass objectEnumerator];
NSString *strURLDate = [NSString stringWithFormat:#"%#%#", #"http://wwwaddress/getinfo.php?choice=5&schoolname=",obsSchoolName];
NSArray *observationsArrayDate = [[NSMutableArray alloc] initWithContentsOfURL:[NSURL URLWithString:strURLDate]];
observationListFromSQL = [[NSMutableArray alloc]init];
NSEnumerator *enumForObsDate = [observationsArrayDate objectEnumerator];
id className, dateOfObs;
while (className = [enumForObsClass nextObject])
{
dateOfObs = [enumForObsDate nextObject];
[observationListFromSQL addObject:[NSDictionary dictionaryWithObjectsAndKeys:className, #"obsClass", dateOfObs, #"obsDate",nil]];
}
Yes, you can do that with less code by "folding" the statements into a loop, and using a mutable dictionary:
// Add other items that you wish to retrieve to the two arrays below:
NSArray *keys = #[#"obsClass", #"obsDate"]; // Key in the dictionary
NSArray *choices = #[#1, #5]; // Choice in the URL string
NSMutableArray *res = [NSMutableArray array];
NSMutableArray *observationListFromSQL = [NSMutableArray array];
for (int i = 0 ; i != keys.count ; i++) {
NSNumber *choice = choices[i];
NSString *strURLClass = [NSString stringWithFormat:#"http://wwwaddress/getinfo.php?choice=%#&schoolname=%#", choice, obsSchoolName];
NSArray *observationsArray = [[NSMutableArray alloc] initWithContentsOfURL:[NSURL URLWithString:strURLClass]];
NSEnumerator *objEnum = [observationsArrayClass objectEnumerator];
NSString *key = keys[i];
NSMutableDictionary *dict;
if (res.count < i) {
dict = res[i];
} else {
dict = [NSMutableDictionary dictionary];
[res addObject:dict];
}
id item;
while (item = [objEnum nextObject]) {
[res setObject:item forKey:key];
}
}

opencv ios cvseq storage

I am intending to pre-load all the images that I have stored inside application. Pre-loading of images involves:
Read images from bundle.
Extract object descriptors using cvExtractSurf from opencv framework.
Store IPLImage with corresponding object descriptors and keypoints.
I am having an issue in creating a dictionary containing CvSeq* keys and CvSeq* descs.
Please suggest how to store these values in NSMutableDictionary.
-(void) preloadImages:(NSMutableDictionary *)dictionary{
NSArray *d = [[NSBundle mainBundle] pathsForResourcesOfType:#"png" inDirectory:nil];
CvSURFParams params = cvSURFParams(500, 1);
CvMemStorage* storage = cvCreateMemStorage(0);
for( int i=0;i<[d count];i++){
NSString *searchForMe = #"myapp.app/1";
NSString *s = [[NSString alloc] initWithString:[d objectAtIndex:i]];
NSRange range = [s rangeOfString:searchForMe];
if( range.location != NSNotFound ){
NSMutableDictionary *surfDict = [[NSMutableDictionary alloc] init];
NSString *substring = [s substringFromIndex:range.location];
substring = [substring stringByReplacingOccurrencesOfString:#"myapp.app/" withString:#""];
UIImage *testImage = [UIImage imageNamed:substring];
IplImage *iplTestImage = [OpenCVUtilities CreateGRAYIplImageFromUIImage:testImage];
CvSeq *keys = 0 ;
CvSeq *descs = 0;
cvExtractSURF( iplTestImage, 0, &keys, &descs, storage, params );
[surfDict setObject:(id)testImage forKey:#"uiImage"];
NSLog(#"Image name : %#", substring);
[dictionary setObject:surfDict forKey:[NSString stringWithFormat:#"%d",i]];
[dictionary setObject:(NSObject *)keys forKey:#"keys"]; // error here
[dictionary setObject:(NSObject *)descs forKey:#"descs"]; // error here
[surfDict release];
}
}
}
Create a class that has an instance variable of type cvseq, add your cvseq to the object, and add that class to the dictionary.

potential leak problems

when I build and analize my application , am getting potential leak near the code [array1 release]...why its happening there..?thanks in advance
- (void) touchOnFeaturedCatalog
{
searchId == 2;
//featuredCatalogName = #"23064_Leeds2010";
//NSString *response = [ZoomCatalogAppDelegate getResponseFromServer:[NSString stringWithFormat:#"http://www.zoomcatalog.com/iphone/iphone.php?catalog=%#&iphone=Yes&pdf=No", featuredCatalogName]];
NSString *response = [ZoomCatalogAppDelegate getResponseFromServer:#"http://www.zoomcatalog.com/iphone/supplier.php"];
//NSString *response = [ZoomCatalogAppDelegate getResponseFromServer:#"http://test.atvescape.com/articles.php"];
//NSLog(#"Response = %#", response);
NSArray *array = [response componentsSeparatedByString:#"##"];
[array retain];
for(int i = 0; i < array.count; i++)
{
NSLog(#"Trying outer loop.... %d, %#, %#", i, [array objectAtIndex:i], featuredCatalogName);
NSArray *array4 = [featuredCatalogName componentsSeparatedByString:[array objectAtIndex:i]];
if(array4.count > 1)
{
response = [ZoomCatalogAppDelegate getResponseFromServer:[NSString stringWithFormat:#"http://www.zoomcatalog.com/iphone/catalog_search.php?tid2=%#&iphone=yes", [array objectAtIndex:i]]];
NSArray *array3= [response componentsSeparatedByString:#"<br>"];
//baseURL = [NSString stringWithFormat:#"%#", [array3 objectAtIndex:0]];
global_ContentString = [NSString stringWithFormat:#"%#", [array3 objectAtIndex:2]];//(searchId == 1 ? [array objectAtIndex:2] : ([array objectAtIndex: isLineNameSearch ? 2 : 1]))];
[global_ContentString retain];
// NSLog(#"baseURL = %#", global_ContentString);
NSArray *array1 = [global_ContentString componentsSeparatedByString:#"###"];
for(int j = 0; j < array1.count; j++)
{
NSArray *array2 = [[array1 objectAtIndex:j] componentsSeparatedByString:#"##"];
NSString *str = [NSString stringWithFormat:#"%#", [array2 objectAtIndex:0]];
str = [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([str caseInsensitiveCompare:featuredCatalogName] == NSOrderedSame)
{
global_ContentString = [ZoomCatalogAppDelegate getResponseFromServer:[NSString stringWithFormat:#"http://www.zoomcatalog.com/iphone/iphone.php?catalog=%#&iphone=Yes&pdf=No", [array2 objectAtIndex:5]]];
baseURL = [NSString stringWithFormat:#"%#", [[global_ContentString componentsSeparatedByString:#"<br>"] objectAtIndex:0]];
//global_ContentString = [NSString stringWithFormat:#"%#", [[global_ContentString componentsSeparatedByString:#"<br>"] objectAtIndex:1]];
[global_ContentString retain];
[global_MainPageController presentModalViewController:global_FullPageController animated:YES];
//NSLog(#"$$$$$$$$$$$$$$$$$$$$$$ Catalog id = %# $$$$$$$$$$$$$$$$$$$$$$$$$$", [array2 objectAtIndex:5]);
//[array1 release];memory leak
return;
}
// NSLog(#"Trying inner loop.... %d, %#, %#", j, str, featuredCatalogName);
}
}
// if([[array objectAtIndex:i] com
}
[array release];
return;
}
sorry for all..
If you are only using an object locally (within the method in which it is created) you can autorelease it. Objects that are created or returned by convenience methods available until the end of the function call. Unless you need the objects elsewhere, I suggest ditching the retain calls. The rule of thumb is that whenever you call alloc, new, retain, or copy you mist release the object. However, if you use a convenience method, The returned object is autogenerate for you.
It seems that you call [global_ContentString retain]; but then fail to call a corresponding release.