Preferred way to make a mutable copy of a non-mutable object? - ios

There are 2 choices (possibly more). Using NSSet as an example:
NSMutableSet * mutableSet = [ NSMutableSet setWithSet:nonMutableSet ] ;
or
NSMutableSet * mutableSet = [ [ nonMutableSet mutableCopy ] autorelease ] ;
Is there any difference between these two implementations? Is one "more efficient" at all? (Other examples would be NSArray/NSMutableArray and NSDictionary/NSMutableDictionary

Benchmark fun! :)
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{ #autoreleasepool {
NSMutableSet *masterSet = [NSMutableSet set];
for (NSInteger i = 0; i < 100000; i++) {
[masterSet addObject:[NSNumber numberWithInteger:i]];
}
clock_t start = clock();
for (NSInteger i = 0; i < 100; i++) {
#autoreleasepool {
[NSMutableSet setWithSet:masterSet];
}
}
NSLog(#"a: --- %lu", clock() - start);
sleep(1);
start = clock();
for (NSInteger i = 0; i < 100; i++) {
#autoreleasepool {
[[masterSet mutableCopy] autorelease];
}
}
NSLog(#"b: --- %lu", clock() - start);
return 0;
} }
On my machine (10.7), setWithSet: is ~3x slower than -mutableCopy (Does somebody want to try on iOS 5? :) )
Now, the question is: why?
-mutableCopy is spending most of its time in CFBasicHashCreateCopy() (see CFBasicHash.m). This appears to be copying the hash buckets directly, with no rehashing.
Running Time Self Symbol Name
256.0ms 61.5% 0.0 -[NSObject mutableCopy]
256.0ms 61.5% 0.0 -[__NSCFSet mutableCopyWithZone:]
256.0ms 61.5% 0.0 CFSetCreateMutableCopy
255.0ms 61.2% 156.0 CFBasicHashCreateCopy
97.0ms 23.3% 44.0 __CFSetStandardRetainValue
-setWithSet is enumerating through each value of the set, and then adding it to the new set. From the implementation of CFBasicHashAddValue (again in CFBasicHash.m), it looks like it is rehashing each value in the set.
Running Time Self Symbol Name
1605.0ms 86.0% 0.0 +[NSSet setWithSet:]
1605.0ms 86.0% 2.0 -[NSSet initWithSet:copyItems:]
1232.0ms 66.0% 68.0 -[__NSPlaceholderSet initWithObjects:count:]
1080.0ms 57.8% 299.0 CFBasicHashAddValue
324.0ms 17.3% 28.0 -[NSSet getObjects:count:]
272.0ms 14.5% 75.0 __CFBasicHashFastEnumeration
This rehash makes sense at the CFSet level. CFSets take a CFSetHashCallBack in the callBacks parameter; thus, two CFSets of CFNumbers could have a different hashing routine specified. Foundation's NSSet uses CFSet under-the-hood, and has a CFSetHashCallBack function which invokes -[NSObject hash]. (Although I guess that Apple could optimize this case and avoid the rehash when two sets have the same hash callback).
Note that this benchmark is for NSSet (of NSNumbers) only, other collection classes may have different performance characteristics.

Related

Array of dictionary value changes is reflect in all the values

I have NSMutableArray and Added NSMutableDictionary
If i update one value for specific row then all the values changes in NSMutableDictionary.
NSIndexPath *qtyIndex
-(void)demoDefaultCartValues{
[dict_CartItems setValue:#"Item 1 KK Demo" forKey:#"DIC_PRODUCT_NAME"];
[dict_CartItems setValue:#" KK Demo" forKey:#"SELLER_NAME"];
[dict_CartItems setValue:#"1" forKey:#"QTY_VALUE"];
[dict_CartItems setValue:#"42" forKey:#"SIZE_VALUE"];
[dict_CartItems setValue:#"1250" forKey:#"PRICE_VALUE"];
[dict_CartItems setValue:#"1500" forKey:#"DISCOUNT_VALUE"];
for (int i = 0; i <= 5; i++) {
[cartListArray addObject:dict_CartItems];
}
}
#pragma mark - DropDown Delegate
-(void)dropDownView:(UIView *)ddView AtIndex:(NSInteger)selectedIndex{
[[cartListArray objectAtIndex:qtyIndexPath.row] setValue:[sizeArrayList objectAtIndex:selectedIndex] forKey:#"QTY_VALUE"];
NSLog(#"What %#",cartListArray);
}
If I update qty 1 to 5 then all dictionary values QTY_Value changes to 5.
Use new Objective-C features (more than 5 years old) to make this more readable. And add six different mutable dictionaries to the array:
NSDictionary* dict = { #"DIC_PRODUCT_NAME":#"Item 1 KK Demo",
#"SELLER_NAME":#" KK Demo",
#"QTY_VALUE": #"1",
etc.
};
for (NSInteger i = 0; i < 6; ++i)
[cartListArray addObject: [dict mutableCopy]];
and later:
-(void)dropDownView:(UIView *)ddView atIndex:(NSInteger)selectedIndex{
cartListArray [qtyIndexPath.row] [#"QTY_VALUE] = sizeArrayList [selectedIndex];
}
cartListArray should be declared as
NSMutableArray <NSMutableDictionary*> *cartListArray;
Now what I would really recommend that you don't store a dictionary at all, but declare a model class. So you don't have to use strings for quantity etc. but NSInteger. Also that cartListArray isn't mutable if you don't want to modify it after it has been initialised. Keep things immutable whenever you can.
This is obvious
when you are adding NSMutableDictionary to array, array have the reference of that dictionary inside.
Now what you are doing is inserting same dictionary multiple times in array. so when you change a single object in array. all the places are get effected. keeping same object of Dictionary always causes this issue.
Solution for this problem is create a new object every time before you insert into array.
Hope it is helpfult to you
The problem is that your code uses the same dictionary and it's a refrence value , so it's a shallow copy of the same one , you may create a new one every iteration
-(void)demoDefaultCartValues{
for (int i = 0; i <= 5; i++) {
NSMutableDictionary*dict_CartItems = [ NSMutableDictionary new];
[dict_CartItems setValue:#"Item 1 KK Demo" forKey:#"DIC_PRODUCT_NAME"];
[dict_CartItems setValue:#" KK Demo" forKey:#"SELLER_NAME"];
[dict_CartItems setValue:#"1" forKey:#"QTY_VALUE"];
[dict_CartItems setValue:#"42" forKey:#"SIZE_VALUE"];
[dict_CartItems setValue:#"1250" forKey:#"PRICE_VALUE"];
[dict_CartItems setValue:#"1500" forKey:#"DISCOUNT_VALUE"];
[cartListArray addObject:dict_CartItems];
}
}
Or you can use copy / mutableCopy
for (int i = 0; i <= 5; i++) {
[cartListArray addObject:[dict_CartItems mutableCopy]];
}

Finding the lowest NSInteger from NSArray

I am trying to return the lowest number in an array.
Parameter: arrayOfNumbers - An array of NSNumbers.
Return: The lowest number in the array as an NSInteger.
The code I have thus far doesn't give me any errors, but does not pass the unit tests. What am I doing wrong?
- (NSInteger) lowestNumberInArray:(NSArray *)arrayOfNumbers {
NSNumber* smallest = [arrayOfNumbers valueForKeyPath:#"#min.self"];
for (NSInteger i = 0; i < arrayOfNumbers.count; i++) {
if (arrayOfNumbers[i] < smallest) {
smallest = arrayOfNumbers[i];
}
}
NSInteger smallestValue = [smallest integerValue];
return smallestValue;
}
This is the unit test:
- (void) testThatLowestNumberIsReturned {
NSInteger lowestNumber = [self.handler lowestNumberInArray:#[#3, #8, #-4, #0]];
XCTAssertEqual(lowestNumber, -4, #"Lowest number should be -4.");
lowestNumber = [self.handler lowestNumberInArray:#[#83, #124, #422, #953, #1004, #9532, #-1000]];
XCTAssertEqual(lowestNumber, -1000, #"Lowest number should be -1000.");
}
This method
NSNumber* smallest = [arrayOfNumbers valueForKeyPath:#"#min.self"];
will already determine the smallest number in the array, so the loop inside the method is superfluous (on top of being plain wrong, as #vikingosegundo notices).
you are comparing objects with c types, resulting im pointer addresses being compared with an int.
Beside the fact your smallest is already the smallest, as you used the KVC collection operator #min.self (see Glorfindel answer), the following code shows you correct comparison
if (arrayOfNumbers[i] < smallest)
should be
if ([arrayOfNumbers[i] compare:smallest] == NSOrderingAscending)
or
if ([arrayOfNumbers[i] integerValue] < [smallest integerValue])

Call objects by their string name where the strings are built dynamically [duplicate]

This question already has answers here:
Objective C Equivalent of PHP's "Variable Variables" [duplicate]
(2 answers)
Closed 8 years ago.
For my iOS app, I created a class named Tile which is a subclass of UIImageView.
The tiles are displayed in a kind of an array of 6 rows and 5 column.
I previously created 30 instances of my Tile class. These instances are all named this way: RiCj where i is the row number and j is the column number.
I would like to create a for loop where I would apply a specific treatment to each of my tiles (basically, I want to display the tiles where displayTile is an instance method of the class Tile).
I would love to do something like (I know the code below is incorrect):
for (int i = 1; i <= numberOfRows ; j++) {
for (int j = 1; j <= numberOfColumns ; j++) {
[self.RiCj displayTile];
}
}
I don't know how to do a call to my tiles based on their dynamic string title.
Yes, technically, it is possible - you may use Key-Value Coding like this:
for (int i = 1; i <= numberOfRows; i++) {
for (int j = 1; j <= numberOfColumns; j++) {
NSString* tileName = [NSString stringWithFormat:#"R%dC%d", i, j];
[[self valueForKey:tileName] displayTile];
}
}
But you should not. It won't be a clean solution. Array is a more natural choice here.
Yes, you can actually access a property of a class dynamically by creating a string naming the property then using KVC like so:
NSString *propertyName = [NSString stringWithFormat:#"R%dC%d", i, j];
tile = [self valueForKey:propertyName];
But should you? No, not in this case. It's a horrible hack when the perfectly nice alternative of making an array (or array of arrays) is available.
Here's what array of array creation and access might look like (by using handy Objective C literals for arrays):
NSArray *tiles = #[
#[ tile0C0, tile0C1, tile0C2 ],
#[ tile1C0, tile1C1, tile1C2 ],
#[ tile2C0, tile2C1, tile2C2 ],
];
for (int i = 0; i < numberOfRows ; j++) {
for (int j = 0; j < numberOfColumns ; j++) {
tile = tiles[j][i];
// do stuff with tile
}
}
If I understand correctly, you're trying to access the instances by their variable names dynamically. You can't do that, as your variable name is designed for you, the programmer, and is not available at runtime.
What you can do, however, is to keep a list of your created instances in an array somewhere, and simply iterate over that array when you need to access them.
Alternatively, if you created the 30 tiles as 30 different properties, you could use some dynamic code to get them. At that point, however, I would strongly recommend to use the array technique.

how to include index number in a class name

I am making a program where I need to loop through an array with a list of letters. I want the letters to be shown on their specific label. I have therefore created an outlet of each (about 38) and given them the name "alif01", "alif02", etc.
for (int i = 0; i < [arabicLetters count]; i++) {
int num = i;
NSString *letterString = [arabicLetters objectAtIndex:i];
NSLog(#"alif0%d is %#", num, letterString);
alif0**[i]**.text = arabicLetters[i];
}
is it possible to use the index [i] instead of writing it all manually?
You should not have 38 IBOutlet properties for this. You should have an array (possibly an IBOutletCollection) so that you can loop over the array / index into the array.
While technically you can create a key name and use KVC valueForKey: (appending strings / string format), the array approach is a much better solution.
Indeed, as you already have a loop, you would be better served by creating the labels in the loop directly, then you know you have the correct number. This is particularly beneficial later, when you change the contents of arabicLetters (though that sounds like it isn't a concern in this particular case).
Try with below code:
for (int i = 0; i < [arabicLetters count]; i++) {
NSString *letterString = [arabicLetters objectAtIndex:i];
NSString *propertyName = [NSString stringWithFormat:#"alif0%d.text",i];
[self setValue:letterString forKeyPath:propertyName];
}

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