upload many files and save in CoreData - ios

Using the API I get 500 pictures, upload them asynchronously. Then I want to keep all these pictures in CoreData, but the application crashes due to insufficient memory.
When upload finished i call method createFromBlock
+(id)createFromBlock:(MRBlock *)block{
ManagedBlock *item = [ManagedBlock MR_createInContext:DefaultContext];
item.id = #(block.id);
item.name = block.name;
item.slidesInBlock = #(block.slidesInBlock);
item.sizeBlock = block.sizeBlock;
item.desc = block.desc;
item.imagePath = block.imagePath;
item.image = [MRUtils transformedValue:block.image];
item.price = block.price;
int i = 0;
ManagedItem *new = nil;
for (MRItem *lol in block.items){
NSLog(#"%i", i);
new = [ManagedItem createFromItem:lol];
new.block = item;
[item addItemsObject:new];
new = nil;
i++;
}
[DefaultContext MR_saveWithOptions:MRSaveSynchronously completion:nil];
return item;
}
In foreach block.items app is crashed. approximately after 150-160 positions.
If i comment new = [ManagedItem createFromItem:lol]; - app dont crash
+(id)createFromItem:(MRItem *)object{
ManagedItem *item = [ManagedItem MR_createInContext:DefaultContext];
item.id = #(object.id);
item.title = object.title;
item.detail = object.detail;
item.imagePath = object.imagePath;
item.image = [MRUtils transformedValue:object.image];
return item;
}

First, you should not load all your data and then save it. You should load in small batches, and save each batch.
However, for your specific example, I believe you can get away with turning each object into a fault after saving.
I have never used magical record, and have no idea if it will allow you do to contexty things without calling its methods. I looked at it once, but it hides way too many details for me... Unless you are doing the most basic things, Core Data can be quite complex, and I want to know everything that is going on with my core data code.
+(id)createFromBlock:(MRBlock *)block{
#autoreleasepool {
ManagedBlock *item = [ManagedBlock MR_createInContext:DefaultContext];
item.id = #(block.id);
item.name = block.name;
item.slidesInBlock = #(block.slidesInBlock);
item.sizeBlock = block.sizeBlock;
item.desc = block.desc;
item.imagePath = block.imagePath;
item.image = [MRUtils transformedValue:block.image];
item.price = block.price;
NSUInteger const batchSize = 10;
NSUInteger count = block.items.count;
NSUInteger index = 0;
while (index < count) {
#autoreleasepool {
NSMutableArray *newObjects = [NSMutableArray array];
for (NSUInteger batchIndex = 0;
index < count && batchIndex < batchSize;
++index, ++batchIndex) {
MRItem *lol = [block.items objectAtIndex:index];
ManagedItem *new = [ManagedItem createFromItem:lol];
new.block = item;
[item addItemsObject:new];
[newObjects addObject:new];
}
[DefaultContext MR_saveWithOptions:MRSaveSynchronously completion:nil];
for (NSManagedObject *object in newObjects) {
// Don't know if MagicalRecord will like this or not...
[DefaultContext refreshObject:object mergeChanges:NO];
}
}
}
return item;
}
Basically, that code processes 10 objects at a time. When that batch is done, the context is saved, and then turns those 10 objects into faults, releasing the memory they are holding. The auto-release-pool makes sure that any object created in the containing scope are released when the scope exits. If no other object holds a retain on the objects, then they are deallocated.
This is a key concept when dealing with large numbers or just large objects in Core Data. Also, understanding the #autoreleasepool is extremely important, though people who have never used MRR do not appreciate its benefit.
BTW, I just typed that into the editor, so don't expect it to compile and run with a simple copy/paste.

Related

Expected method to read array element not found on object of type NSDictionary*

I know there's a lot of questions like this around, but I think my situation's a tad different.
int i = 0;
while (_data[#"VerticalState%i", i] != nil) {
// do things
i++;
}
For example, one 'level' that has 3 VerticalState properties will be implemented as such: VerticalState0, VerticalState1, VerticalState2.
I want to read in those values using that while loop condition above, and it should stop when i = 3. How can I make the idea of that code above work (with some other configuration obviously). FYI, _data is an NSDictionary* instance variable, already loaded with the plist information.
You appear to want to create a dictionary key from a string format. You need to use NSString stringWithFormat:.
while (_data[[NSString stringWithFormat:#"VerticalState%i", i]] != nil) {
Though it would be better to write the loop like this:
int i = 0;
while (1) {
NSString *key = [NSString stringWithFormat:#"VerticalState%i", i];
id value = _dict[key];
if (value) {
// do things
i++;
} else {
break;
}
}

Firebase sort query gives (unsorted) dictionary results

So I'm sure I'm missing something here, but when I wish to do a query for, say, the top 10 scores in a certain game, Firebase returns the top 10 scores, however since they are then gives back as a dictionary (key generated with childByAutoId), they are 'unsorted' when received on the client side (so to display a top 10 you have to sort them again)...
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
FIRDatabaseQuery *jungleHighscoresQuery = [[[ref child:#"jungleScores"] queryOrderedByChild:#"score"] queryLimitedToLast:4];
[jungleHighscoresQuery observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot){
NSDictionary *postDict = snapshot.value;
NSLog(#"%#", postDict);
}];
The above code gives the following output (these are the top 4 scores among the ~20 in the database):
{
"-KIUhe_9TLQoy_zNbJT0" = {
score = 290;
userid = oMqoPGJYsGWHffYx8N6vDk3Osh72;
};
"-KIUj8VUyNMgyZ135dI_" = {
score = 560;
userid = oMqoPGJYsGWHffYx8N6vDk3Osh72;
};
"-KIUjK15Gy9PRB_JBWOA" = {
score = 240;
userid = oMqoPGJYsGWHffYx8N6vDk3Osh72;
};
"-KIUlZ1a03r7bjPYNueG" = {
score = 740;
userid = oMqoPGJYsGWHffYx8N6vDk3Osh72;
};
}
I know it's an easy task to just sort it all again, but it seems weird to have to do.
There was an easy solution after all, just had to grab the enumerator instead of the dictionary:
NSEnumerator *enumerator = snapshot.children;
and then just iterate through them with [enumerator nextObject]

How to put NSDictionary inside NSDictionary into UITableView?

I receive following NSDictionary from my server. I don't know how to access each row and put them in UITableView. I need a for loop or something to access each of them one by one: I get following when I say:
NSLog(#"%#",dict);
{
chores = (
{
"first_due_date" = "2016-03-12";
frequency = weekly;
id = 1;
name = TEST;
parent = Blah;
"previous_chores" = (
);
},
{
"first_due_date" = "2016-03-12";
frequency = weekly;
id = 2;
name = TEST2;
parent = Blah;
"previous_chores" = (
);
},
{
"first_due_date" = "2016-03-12";
frequency = weekly;
id = 3;
name = TEST3;
parent = Blah;
"previous_chores" = (
);
}
);
}
I guess you can directly use the array without split it like following:
Suppose you have an array with dictionary and inside the dictionary as your data and kiran said:
NSArray *arrChores = [dict valueForKey #"Chores"];
Your array look like 0th index is:
{
"first_due_date" = "2016-03-12";
frequency = weekly;
id = 1;
name = TEST;
parent = Blah;
"previous_chores" = (
);
}
so you can print the name like following in cellForrowIndex:
NSLog(#"=== %#",[[arrChores objectAtIndex:indexPath.row]valueForKey:#"name"])
More easy and less maintain :)
Create a model for Chores.
Looking at the dictionary.
Chores is an array consisting of dictionaries in it.
So you can have array like this
arrChoresGlobal = [NSMutableArray new];
NSArray *arrChores = [dict valueForKey #"Chores"];
Then iterate this array and have a model.
like below
for(NSDictionary *dctChore in arrChores)
{
SomeModel *obj = [SomeModel new];
obj.first_due_date = [dctChore valueForKey:#"first_due_date"];
[arrChoresGlobal addObject:obj];
}
Then use this array count in numberOfRows in tableview
return [arrChoresGlobal count];
and in cellForRowAtIndexPath
SomeModel *obj = [arrChoresGlobal objectAtIndex:indexPath.row];
Edit :- Longer but systematic way :)

Parsing JSON with NSDictionary

I am working on an app which fetches data from a server. I convert this data to XML and then parse it using XMLDictionary. My problem is counting the number of objects inside the dictionary.
"finance_fee_collection" = (
{
amount = "790.00";
"due_date" = "2015-06-04";
name = "Third Payment";
},
{
amount = "790.00";
"due_date" = "2014-12-11";
name = "First Payment";
},
{
amount = "740.00";
"due_date" = "2015-07-06";
name = "third payment";
}
);
Counting the above number of objects yields 3, which is true. But counting the following also results 3.
"finance_fee_collection" = {
amount = "740.00";
"due_date" = "2015-07-06";
name = "third payment";
};
What I want is to count the number of "finance_fee_collection" items, such that the first one results 3 and the second one results 1. Is there anyway I can approach this goal?
The first one seems to be an array, the second a dictionary:
if([finance_fee_collection iskindOfClass:[NSDictionary class]]){
count = 1;
} else {
count = result.count;
}
something similar to this should work. :) hope it is helpful.
This doesn't make sense really, since both NSArray and NSDictionary implement the count method, which should return 3 for both structures:
id obj = #[#1 ,#2, #3]; // This is an NSArray
NSLog(#"%lu", (unsigned long)[obj count]); // Outputs 3
obj = #{ #1:#1,
#2:#2,
#3:#3
}; // now it's an NSDictionary
NSLog(#"%lu", (unsigned long)[obj count]); // Outputs 3
Were you using [object count] to get the count?
Either way, I wouldn't assume that count would be 1 simply because the data structure is a dictionary (you didn't count it)

How to correctly add large dataset in CoreData?

I have a huge NSArray (4.000.000 objects) that I want to save into Core Data.
Because I use ARC and the autorelease pool may get too big, I partitioned the process into multiple loops (so autorelease pools can have the chance to drain themselves).
In the following code I use a manager (clMan) to add items from the dictionaries inside the array(regions). The dictionaries contain two string fields which are parsed into scalar integers.
Code for partitioning the data into multiple loops
int loopSize = 50000;
int loops = 0;
int totalRepetitions = regions.count;
loops = totalRepetitions / loopSize;
int remaining = totalRepetitions % loopSize;
loops += 1;
for (int i = 0; i < loops; i++) {
int k = 0;
if (i == 0) k = 1;
if (i == (loops - 1))
{
// Last loop
for (long j = i * loopSize + k; j < i * loopSize + remaining; j++) {
[clMan addItemWithData:[regions objectAtIndex:j]];
}
[clMan saveContext];
break;
}
// Complete loops before the last one
for (long j = i * loopSize + k; j < (i + 1) * loopSize; j++) {
[clMan addItemWithData:[regions objectAtIndex:j]];
}
[clMan saveContext];
NSLog(#"Records added : %d", i * loopSize);
}
NSLog(#"Finished adding into core data");
Code for adding the data into core data:
-(void)addItemWithData:(NSDictionary *)data
{
MyRegion *region = [NSEntityDescription
insertNewObjectForEntityForName:#"MyRegion"
inManagedObjectContext:self.context];
region.index = [((NSString *)[data objectForKey:REGION_INDEX]) intValue];
region.id = [((NSString *)[data objectForKey:REGION_ID]) intValue];
}
The program crashes when it reaches the 1 500 000 index. The crash does not seem to happen because of parsing issues / logic.
Can anyone tell me if my logic is bad or what is the correct way to add this amount of data in CoreData?
After each loop, try calling NSManagedObjectContext.reset to "forget" the local copies in the MOC. Otherwise these might not be cleared and are causing a problem.
The WWDC 2012 code samples on iCloud have a method called seedStore where they migrate a local core data SQL database to the iCloud one - using a batch size of 5000 records and there it explicitly states that:
if (0 == (i % batchSize)) {
success = [moc save:&localError];
if (success) {
/*
Reset the managed object context to free the memory for the inserted objects
The faulting array used for the fetch request will automatically free
objects with each batch, but inserted objects remain in the managed
object context for the lifecycle of the context
*/
[moc reset];
} else {
NSLog(#"Error saving during seed: %#", localError);
break;
}
}
(Here i is the current index of the batch, thus i % batchSize == 0 if we start a new batch)

Resources