In an app that has many different UITableViews, I have found myself frequently using temporary arrays to import the data used to populate the table view, determine number of rows, sections, headers, footers, etc. I'm wondering if, because these array need to be created for each cell in the table over and over if declaring the static so they don't need to be created again will help performance, because right now these arrays are being created in cellForRowAtIndexPath:, numberOfRowsInSections:, numberOfSectionsInTableView:,footerForSection:`. Would declaring this many static arrays (which might contain a decent amount of information, say a couple thousand doubles and a couple hundred strings), in the long run, help or hurt me? I know that a static array stays in memory for the course of the application's life, so will this many static arrays be detrimental? Assuming that this process occurs in 4-5 view controllers across the course of the app, we are talking about 15-20 copies of this array just sitting around. What is my best option here? Thanks
Edit: I am using a singleton which holds the values. The real reason for the temporary arrays is to keep the code clean. I can do something like
dataArray = [[SingletonDataController sharedSingleton] dataArray]
objectAtIndex:CURRENTLY_SELECTED_DATA_INDEX;
then
myTitleString = [dataArray objectAtIndex:keyTitleStringIndexKey];
instead of grouping it all into one unreadable statement like:
myTitleString = [[[[SingletonDataController sharedSingleton] dataArray]
objectAtIndex:CURRENTLY_SELECTED_INDEX] objectAtIndex:keyTitleStringIndexKey];
I have performed some tests of my own, comparing the time it takes to create the table view with/without static initialization. These are the results:
2012-01-29 18:31:57.539 XXXXXXX[711:707] static average: 0.058798
2012-01-29 18:31:57.543 XXXXXXX[711:707] nonstatic average: 0.058395
As you can see, the static initialization is actually slower than the non-static, but only by a few ten-thousandths of a second. This is probably just a product of inaccurate measurement, but the results say enough to convince me that the difference is small enough to dismiss. Mystery solved.
When you do the above you are not actually creating a new array, just grabbing a pointer to that array. You are not copying the actual data.
By keeping your code clean you are losing only the performance of creating memory for a pointer and assigning a pointer a value. So no, you are not losing performance.
The idea of keeping your code clean is much more important than this marginal difference in an extra pointer here and there.
Edit:
I did some testing between the two and as expected, both options perform very similar.
NSMutableArray *data1 = [[NSMutableArray alloc] init];
NSMutableArray *data2 = [[NSMutableArray alloc] init];
NSArray *all = [[NSArray alloc] initWithObjects:data1,data2,nil];
for(int i=0;i<1000;i++)
{
[data1 addObject:[[NSNumber alloc] initWithInt:arc4random()]];
[data2 addObject:[[NSNumber alloc] initWithInt:arc4random()]];
}
double startTime = CACurrentMediaTime();
for(int i=0;i<1000;i++)
{
NSArray *get1 = [all objectAtIndex:0];
NSArray *get2 = [all objectAtIndex:1];
//int get1Index = arc4random() % [get1 count];
//int get2Index = arc4random() % [get2 count];
//NSLog(#"Object at %d: %f", get1Index, [[get1 objectAtIndex:get1Index] doubleValue]);
//NSLog(#"Object at %d: %f", get2Index, [[get2 objectAtIndex:get2Index] doubleValue]);
NSLog(#"Object at %d: %f", i, [[get1 objectAtIndex:i] doubleValue]);
NSLog(#"Object at %d: %f", i, [[get2 objectAtIndex:i] doubleValue]);
}
NSLog(#"Time with temp array:%f", CACurrentMediaTime() - startTime);
startTime = CACurrentMediaTime();
for(int i=0;i<1000;i++)
{
//int get1Index = arc4random() % [[all objectAtIndex:0] count];
//int get2Index = arc4random() % [[all objectAtIndex:1] count];
//NSLog(#"Object at %d: %f", get1Index, [[[all objectAtIndex:0] objectAtIndex:get1Index] doubleValue]);
//NSLog(#"Object at %d: %f", get2Index, [[[all objectAtIndex:1] objectAtIndex:get2Index] doubleValue]);
NSLog(#"Object at %d: %f", i, [[[all objectAtIndex:0] objectAtIndex:i] doubleValue]);
NSLog(#"Object at %d: %f", i, [[[all objectAtIndex:1] objectAtIndex:i] doubleValue]);
}
NSLog(#"Time without temp array:%f", CACurrentMediaTime() - startTime);
//With random access
//2012-01-28 13:44:12.721 test[23164:f803] Time with temp array:0.924193
//2012-01-28 13:44:13.641 test[23164:f803] Time without temp array:0.919250
//2012-01-28 13:44:44.892 test[23191:f803] Time with temp array:0.926337
//2012-01-28 13:44:45.812 test[23191:f803] Time without temp array:0.920447
//With incremental access
//2012-01-28 13:46:43.948 test[23231:f803] Time with temp array:0.935009
//2012-01-28 13:46:44.927 test[23231:f803] Time without temp array:0.978455
//2012-01-28 13:47:40.317 test[23254:f803] Time with temp array:1.173752
//2012-01-28 13:47:41.307 test[23254:f803] Time without temp array:0.989263
The commented out sections are the sections I used for testing random access, for the incremental access I used the current code. Without temp arrays is a fractions quicker, but not noticeably. Not enough to sacrifice readability. I guess that is just the process of writing it out to a variable that slows it down, but, at the same time, having a temp array that is not embedded is much quicker. If you were using the embedded array many times, you would have to do 2 memory accesses instead of 1. So if you are going to use the embedded array several times I imagine the gain would significantly compensate for the loss of using a temp array.
Related
I'm doing an RSVP reading project app where it blinks words on the screen. You can set the word chunk size (how many words you want displayed at a time) to either 1, 2, or 3. I got it working for 1 word by having my paragraph in a string and doing:
[self.textInput componentsSeparatedByString:#" ";
This makes me an array of words that I can use to blink one word at a time. How would I be able to do this with displaying 2 words at a time? Is there a way I can use this function again to do it differently, or should I iterate over this word array and make a new one with 2 word strings?
Any help or advice would be greatly appreciated as to what the best practice would to get this done. Thanks.
just like keith said create an array
NSArray *allwordsArray = [self.textInput componentsSeparatedByString:#" "];
Now you got all the info you need. Meaning you got the array with every word in it. Now its just a matter of putting it together. (I haven't tested this code)
NSMutableArray *twoWordArray = [[NSMutableArray alloc] init];
int counter=0;
for (int i=0; i<[allwordsArray count]; i++)
{
if (counter >= [allwordsArray count]) break;
NSString *str1 = [NSString stringwithformat#"%#", [allwordsArray objectAtIndex:counter]];
counter++;
if (counter >= [allwordsArray count]) break;
NSString *str2 = [NSString stringwithformat#"%#", [allwordsArray objectAtIndex:counter]];
NSString *combinedStr = [NSString stringwithformat#"%# %#", str1,str2];
[twoWordArray addObject: combinedStr];
counter++;
}
You have broken the string into components, which is on the right track. You could then make a smaller array that only includes components until you reach the chunk size. The final step would be to rejoin the string.
NSArray *components = [self.textInput componentsSeparatedByString:#" "];
NSRange chunkRange = NSMakeRange(0, chunkSize);
NSArray *lessComponents = [components subarrayWithRange:chunkRange];
NSString *newString = [lessComponents componentsJoinedByString:#" "];
The y axis represents the the average access time (in ns) to each node in the list/array (total time to access all elements divided by the number of elements).
The x axis represents the number of elements in the array being iterated over.
Where red is an implementation of NSMutableArray and blue is my linked list (CHTape).
In each outer loop each list/array has a empty string #"" appended to it. In the inner loops each string in each list/array is retrieved, this is timed and recorded. After everything the times our outputted in a Wolfram Language output to produce a plot.
How does NSMutableArray achieve such amazing and consistent results? How can one achieve similar?
My NSFastEnumeration Implementation:
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])stackBuffer count:(NSUInteger)len
{
if (state->state == 0)
{
state->state = 1;
state->mutationsPtr = &state->extra[1];
state->extra[0] = (unsigned long)head;
}
CHTapeNode *cursor = (__bridge CHTapeNode *)((void *)state->extra[0]);
NSUInteger i = 0;
while ( cursor != nil && i < len )
{
stackBuffer[i] = cursor->payload;
cursor = cursor->next;
i++;
}
state->extra[0] = (unsigned long)cursor;
state->itemsPtr = stackBuffer;
return i;
}
Complete Testing Code:
NSMutableArray *array = [NSMutableArray array];
CHTape *tape = [CHTape tape];
unsigned long long start;
unsigned long long tapeDur;
unsigned long long arrayDur;
NSMutableString * tapeResult = [NSMutableString stringWithString:#"{"];
NSMutableString * arrayResult = [NSMutableString stringWithString:#"{"];
NSString *string;
int iterations = 10000;
for (int i = 0; i <= iterations; i++)
{
[tape appendObject:#""];
[array addObject:#""];
// CHTape
start = mach_absolute_time();
for (string in tape){}
tapeDur = mach_absolute_time() - start;
// NSArray
start = mach_absolute_time();
for (string in array){}
arrayDur = mach_absolute_time() - start;
// Results
[tapeResult appendFormat:#"{%d, %lld}", i, (tapeDur/[tape count])];
[arrayResult appendFormat:#"{%d, %lld}", i, (arrayDur/[array count])];
if ( i != iterations)
{
[tapeResult appendString:#","];
[arrayResult appendString:#","];
}
}
[tapeResult appendString:#"}"];
[arrayResult appendString:#"}"];
NSString *plot = [NSString stringWithFormat:#"ListPlot[{%#, %#}]", tapeResult, arrayResult];
NSLog(#"%#", plot);
By forcing ARC off on the link list related files efficiency increased dramatically. It reduced access time from ~70ns to ~14ns. While this is still slower, on average, then NSArray its only, on average, about two times slower, as opposed to ten times slower.
While ARC can make some code faster, in iterative situations adds unnecessary release/retain calls.
Discovered thanks to Greg Parker's comment.
Currently working on finding the best solution to find the smallest time interval between the current time and a date inside of my array.
I have a method that takes a NSArray and returns an NSArray. The method does the following:
Loops through array sorted by time (0 index is closest to current time)
Find smallest interval by finding the delta and comparing
Grab that index, fetch it, return a dictionary
I started to look in to using timeIntervalSinceDate and so on instead of the manual work I am using below.
The array looks something like this:
tideSummary: [{
'type' : 'High Tide',
'pretty' : 'January 16 at 5:13PM EST',
'epoch' : '325267782',
...
}]
Does this code seem to be bloated for what its trying to do? I feel there is a good chunk of duplication to extract certain data based on an index and a key value?
I want to return the closest time out of the array, so I wanted to use timeIntervalSince1970 and do some simple math to find the smallest delta. My array contains a key that returns time in milliseconds
Any suggestions on how I can clean my conditional up so I can still extract: lowTideTime - highTideTime and tideType
Below is my method I am using to extract this information:
- (NSArray *)findUpcomingTides: (NSArray *)arrayOfTideCycles {
NSTimeInterval currentDateInterval;
currentDateInterval = [[NSDate date]timeIntervalSince1970];
NSInteger smallestDelta = currentDateInterval;
NSArray *upcomingTideData = [[NSArray alloc] init];
for (NSInteger i = 0; i < arrayOfTideCycles.count; i++) {
NSDictionary *eachTideSummary = [arrayOfTideCycles objectAtIndex:i];
NSInteger tideDateAsEPOCH = [[eachTideSummary valueForKeyPath:#"epoch"] intValue];
NSInteger dateDelta = tideDateAsEPOCH - smallestDelta;
if (dateDelta < smallestDelta) {
smallestDelta = dateDelta;
int iPlusOne = i+1;
upcomingTide = [arrayOfTideCycles objectAtIndex:i];
NSDictionary *tideTypeDictionary = [arrayOfTideCycles objectAtIndex:i];
tideType = [tideTypeDictionary objectForKey:#"type"];
if([[upcomingTide valueForKeyPath:#"type"] isEqualToString:#"Low Tide"] || [[upcomingTide valueForKeyPath:#"type"] isEqualToString:#"Max Ebb"]){
NSString *lowTidePrettyDateFormat = [upcomingTide valueForKeyPath:#"pretty"];
lowTideTime = [self convertAndFormatDateToTimeWithString:lowTidePrettyDateFormat];
NSDictionary *upcomingHighTide = [arrayOfTideCycles objectAtIndex:iPlusOne];
NSString *highTidePrettyDateFormat = [upcomingHighTide valueForKeyPath:#"pretty"];
highTideTime = [self convertAndFormatDateToTimeWithString:highTidePrettyDateFormat];
} else {
NSString *highTidePrettyDateFormat = [upcomingTide valueForKeyPath:#"pretty"];
highTideTime = [self convertAndFormatDateToTimeWithString:highTidePrettyDateFormat];
NSDictionary *upcomingLowTide = [arrayOfTideCycles objectAtIndex:iPlusOne];
NSString *lowTidePrettyDateFormat = [upcomingLowTide valueForKeyPath:#"pretty"];
lowTideTime = [self convertAndFormatDateToTimeWithString:lowTidePrettyDateFormat];
}
upcomingTideData = [NSArray arrayWithObjects:lowTideTime, highTideTime, tideType, nil];
}
}
return upcomingTideData;
}
Any suggestions on how I can clean this up?
As I understand the question what you dislike is violation of DRY in this parallelism:
NSString *lowTidePrettyDateFormat = [upcomingTide valueForKeyPath:#"pretty"];
lowTideTime = [self convertAndFormatDateToTimeWithString:lowTidePrettyDateFormat];
NSDictionary *upcomingHighTide = [arrayOfTideCycles objectAtIndex:iPlusOne];
NSString *highTidePrettyDateFormat = [upcomingHighTide valueForKeyPath:#"pretty"];
highTideTime = [self convertAndFormatDateToTimeWithString:highTidePrettyDateFormat];
and
NSString *highTidePrettyDateFormat = [upcomingTide valueForKeyPath:#"pretty"];
highTideTime = [self convertAndFormatDateToTimeWithString:highTidePrettyDateFormat];
NSDictionary *upcomingLowTide = [arrayOfTideCycles objectAtIndex:iPlusOne];
NSString *lowTidePrettyDateFormat = [upcomingLowTide valueForKeyPath:#"pretty"];
lowTideTime = [self convertAndFormatDateToTimeWithString:lowTidePrettyDateFormat];
As far as I can tell at first glance, they are absolutely identical (aside from some unimportant local variable names). So factor them out into a method that takes the upcomingTide value and returns an array of the two tide times (or whatever it is that these two bits of code are supposed to produce for you).
Some suggestions:
valueForKeyPath is a very general and complex method that will run relatively slow; objectForKey will be a lot quicker.
If your array contains data in milliseconds, then your code is in trouble. intValue only handles values between about +/- 2 billion. 2 billion milliseconds = 2 million seconds = a bit over a month. Use longLongValue or doubleValue. (If your data is actually in seconds, your code will go wrong some time around 2038).
I don't know exactly what you are trying to do, but this
NSInteger dateDelta = tideDateAsEPOCH - smallestDelta;
if (dateDelta < smallestDelta) {
smallestDelta = dateDelta;
is wrong. Just step through it with the debugger and see how the values change. And I'm sure you'll want an absolute value taken somewhere in there.
Your code will crash if the last array element gives the smallest delta, because you will access the array element one further which is beyond the array.
If you have say 10,000 array elements, five thousand before and five thousand after the current date, you will find the best element so far five thousand times, and each time you do significant work. I'd first find the best array element, and when that is found, get the data that you want.
This is a question related to Are there APIs for custom vibrations in iOS?.
I am able to create custom vibration patterns, but have no control over the intensity.
This is copied over from Kevin Cao's answer that enables custom vibration patterns:
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
NSMutableArray* arr = [NSMutableArray array ];
[arr addObject:[NSNumber numberWithBool:YES]]; //vibrate for 2000ms
[arr addObject:[NSNumber numberWithInt:2000]];
[arr addObject:[NSNumber numberWithBool:NO]]; //stop for 1000ms
[arr addObject:[NSNumber numberWithInt:1000]];
[arr addObject:[NSNumber numberWithBool:YES]]; //vibrate for 1000ms
[arr addObject:[NSNumber numberWithInt:1000]];
[arr addObject:[NSNumber numberWithBool:NO]]; //stop for 500ms
[arr addObject:[NSNumber numberWithInt:500]];
[dict setObject:arr forKey:#"VibePattern"];
[dict setObject:[NSNumber numberWithInt:1] forKey:#"Intensity"];
AudioServicesPlaySystemSoundWithVibration(4095,nil,dict);
The line of code that adds the key #"Intensity" with an int value doesn't do the trick and I don't know how to look inside the AudioServicesPlaySystemSoundWithVibration method to figure it out. What do I have to pass to it so that it actually changes the intensity?
Right now, it doesn't matter if I pass 1, 1000, 0.4 or 0.0001, it's always the same intensity (on an iPhone 4 with iOS7). Can anyone recreate this?
I would like to be able not only to create vibration patterns, but a smooth vibration envelope. How to?
(As this is a research project for instrument design, I am not (yet) concerned with the App store restrictions.)
Change the numberWithInt call into numberWithFloat, and change the intensity so it's between 0 and 1. I thought it was weird when they used an int rather than a float.
Edit: Here's a copy/paste that should work for your code to invoke custom vibration:
#pragma mark - Custom vibration methods
-(void)invokeCustomVibrationWithStartStopTimes:(NSArray*)startStopTimes andIntensity:(float)intensity {
BOOL startOrStop = YES;
NSMutableArray* arr = [#[] mutableCopy];
double time = 0;
for (NSNumber *x in stopStartTimes) {
[arr addObject:x]
startOrStop = !startOrStop;
[arr addObject:#(startOrStop)];
time = [x doubleValue] / 1000.0;
}
AudioServicesPlaySystemSoundWithVibration(4095,nil,{#"VibePattern":arr,#"Intensity":#(intensity)})
[self performSelector:#selector(stop) withObject:nil afterDelay:time];
}
-(void)stop {
AudioServicesStopSystemSound(4095); // stop buzzing the phone
}
For startStopTimes, it should alternate between times started and times stopped. Passing in this array:
#[#(2000), #(1000), #(1000), #(500)]
Will do what the example code did. In this case, it will start for 2000 ms, stop for 1000 ms, start for 1000 ms, and stop for 500 ms.
stop is called to stop the sound. The way I have it set up, it stops sounds after the total amount of time sent in.
You may have noticed I've been using array/number literals rather than using [NSArray arrayWithObjects: ... , nil]; or [NSNumber numberWith...];. This makes your code a lot shorter. Also, I marked the beginning with a #pragma mark. Use that to organize it better. Hope it helps!
I'm creating a NSString that can be used to set a text for UITextView:
for (int score = 0; score < 100; score++){
NSString *newScore = [NSString stringWithFormat:#"Score: %d", score];
[_scoreText setText:newScore];
}
My question is: after the loop is done, will I have 100 objects in memory that I have to deallocate manually, or will ObjectiveC clean the garbage automatically after we leave the scope of the loop?
No.
Both with and without ARC stringWithFormat: will return an autoreleased object.
So all the NSString instanced created with the loop will be deallocated at some point after the loop execution (i.e. when the autorelease pool is drained).
Anyway if you are creating many autoreleased objects inside the loop, you may want to wrap the loop body inside an #autoreleasepool block, so that the strings are released at every iteration
for (int score = 0; score < 100; score++){
#autoreleasepool {
NSString *newScore = [NSString stringWithFormat:#"Score: %d", score];
[_scoreText setText:newScore];
}
}
This can significantly increase the performance of your loop, in case of many iterations.