cocoa: why for-each is faster than for loop? - cocoa

My application read all people in contact in two ways:
for-loop:
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent ();
long count = macContact.addressBook.people.count;
for(int i=0;i<count;++i){
ABPerson *person = [macContact.addressBook.people objectAtIndex:i];
NSLog(#"%#",person);
}
NSLog(#"%f",CFAbsoluteTimeGetCurrent() - startTime);
for-each
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent ();
for(ABPerson *person in macContact.addressBook.people){
NSLog(#"%#",person);
}
NSLog(#"%f",CFAbsoluteTimeGetCurrent() - startTime);
for-each only took 4 seconds to enumerate 5000 people in addressBook, while for-loop took 10 minutes to do the same job.
I want to know why there is a huge difference in performance?

The difference in performance almost certainly has to do with the macContact.addressBook.people part. You're calling that every single time through the for loop but only once with the for-each loop. I'm guessing either the addressBook or the people properties are not returning cached data but rather new data every time.
Try using
NSArray *people = macContact.addressBook.people;
for (int i = 0; i < [people count]; i++) {
NSLog(#"%#", [people objectAtIndex:i];
}
You'll probably find the performance is very similar again.
That said, for-each is faster than for in general. The reason is because a for loop invokes a method send on every single pass through the loop (-objectAtIndex:), whereas for-each can fetch the objects much more efficiently by grabbing them in large batches.
In more recent versions of the OS you can go a step further and use a block-based enumeration method. This looks like
[macContact.addressBook.people enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL stop){
NSLog(#"%#", obj);
}];
For NSArrays this should have very similar performance to a for-each loop. For other data structures such as dictionaries this style can be faster because it can fetch the value along with the key (whereas a for-each only gives you the key and requires you to use a message send to get the value).

Related

NSMutableArray to NSDictionary and then add to an NSArray

My app stores images in an NSMutableArray. I then call those objects and then send them through email in the mailSender.parts section of the code below. The problem is it only adds the first objectatindex when I need to add all objects. I am confused on how to make each image in the self.arrSlidshowImg NSMutableArray add to the vcfPart2 NSDictionary and then add it as an array object so the mailSender.parts will send all images. Any thoughts? I should also note that I did an NSLog to see the results adding this code NSLog(#"VCF: %#", vcfPart2);. The log file showed each value in vcfPart2. So the code is calling each response.
NSDictionary *vcfPart2;
for (int i = 0; i < self.arrSlidshowImg.count; i++) {
NSData *vcfData = [self.arrSlidshowImg objectAtIndex:i];
vcfPart2 = [JFMailSender partWithType:PartTypeFilePart
Message:[vcfData base64EncodedStringWithOptions:0]
ContentType:#"image/jpeg"
ContentTransferEncoding:#"base64"
FileName:[NSString stringWithFormat:#"Individual_%d", i]];
}
mailSender.parts = [NSArray arrayWithObjects:plainPart2,vcfPart2,nil];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[mailSender sendMail];
});
});
The following comes from a simple reading of your code, and among other things I have not looked up JFMailSender:
You declare a variable vcfPart2 to hold a reference to a dictionary
You enter a loop
In the loop you assign a value to vcfPart2, this value is presumably a reference to a dictionary as you report no warnings
Step 3 is executed self.arrSlidshowImg.count times, ice per iteration of the loop
You exit the loop, at this point vcfPart2 will hold the last reference assigned to it, the previous self.arrSlidshowImg.count - 1 having been overwritten
Your concern appears to be that when you then use vcfPart2 it only references one dictionary - but that is all it can ever do, that is it's type.
Maybe you intended to create a dictionary in your loop and then add that dictionary to a mutable array so that after the loop the array contains all the dictionaries?
HTH

Transforming a code to Grand Central Dispatch

I have an array of NSNumbers that have to pass thru 20 tests. If one test fails than the array is invalid if all tests pass than the array is valid. I am trying to do it in a way that as soon as the first failure happens it stops doing the remaining tests. If a failure happens on the 3rd test then stop evaluating other tests.
I am trying to convert the code I have that is serial processing, to parallel processing with grand central dispatch, but I cannot wrap my head around it.
This is what I have.
First the definition of the tests to be done. This array is used to run the tests.
Every individual test returns YES when it fails and NO when it is ok.
#define TESTS #[ \
#"averageNotOK:", \
#"numbersOverRange:", \
#"numbersUnderRange:",\
#"numbersForbidden:", \
// ... etc etc
#"numbersNotOnCurve:"]
- (BOOL) numbersPassedAllTests:(NSArray *)numbers {
NSInteger count = [TESTS count];
for (int i=0; i<count; i++) {
NSString *aMethodName = TESTS[i];
SEL selector = NSSelectorFromString(aMethodName);
BOOL failed = NO;
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation setArgument:&numbers atIndex:2];
[invocation invoke];
[invocation getReturnValue:&failed];
if (failed) {
return NO;
}
}
return YES;
}
This work perfectly but perform the tests sequentially.
How do I do that perform these tests in parallel executing the less amount of tests as needed?
I assume you've spotted dispatch_apply which is the trivial parallel for. You've realised it can't do an early exit. Hence the question.
I'm afraid the answer is you'll need to do some bookkeeping for yourself, but luckily it shouldn't be too hard. To avoid repeating what you've got, pretend I'd turned the stuff inside your loop into:
BOOL failedTest(int);
So your serial loop looks like:
for (int i=0; i<count; i++) {
if(failedTest(i))
return NO;
}
return YES;
Then you might do:
#import <libkern/OSAtomic.h>
volatile __block int32_t hasFailed = 0;
dispatch_apply(
count,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
^(size_t i)
{
// do no computation if somebody else already failed
if(hasFailed) return;
if(failedTest(i))
OSAtomicIncrement32(&hasFailed);
});
return !hasFailed;
So it'll keep starting tests until one of them has previously failed. The OSAtomicIncrement32 just ensures atomicity without requiring a mutex. It'll usually turn into a cheap single instruction. You could get away with just using a BOOL as atomicity isn't really going to be a problem but why not just do it properly?
EDIT: also, you could just use #selector directly and create an array of selectors rather than using NSSelectorFromString with an array of strings, to save lookup time. If your tests are really cheap then consider doing them part serial, part parallel by having the dispatch_apply do, say, count/10 dispatches and having each dispatch do 10 tests. Otherwise GCD will just issue count instances of the block and issuing has an associated cost.

Objective-C EXC_BAD_ACCESS

Ok so I've recently decided to try to teach myself Objective-C (I'm interested in iPhone development), however I've never used C or any of its derivatives before, and as such am running into some problems.
I decided to start out by writing a very basic card application that creates a deck of cards, shuffles the deck, and then displays the cards on the screen using UIButtons, however I'm having a problem with my shuffling algorithm. Every time it gets called I get an EXC_BAD_ACCESS error, which I know means there's something desperately wrong with my code, but I just can't figure out what it is.
- (void) randomize {
NSMutableArray *tmpDeck = [[NSMutableArray alloc] init];
for(Card *tmp in _cards) {
BOOL didInsert = NO;
while(!didInsert) {
NSUInteger random = arc4random_uniform(54);
if([[tmpDeck objectAtIndex:random] isEqual:nil]) {
[tmpDeck insertObject:tmp atIndex:random];
didInsert = YES;
}
}
}
_cards = tmpDeck;
_hasBeenRandomized = YES;
}
_cards is a pointer to an NSMutableArray containing the unshuffled deck of card objects, and _hasBeenRandomized is a boolean (obviously) that keeps track of whether or not the deck has been randomized.
I've tried to use the debugger to work out what exactly is going on here, but I can't even step into the method without my program crashing. This leads me to believe that the problem has to come from the very first line, but it's just a straightforward creation of an NSMutableArray, so I don't know how it could be that. This method is being called from within viewDidLoad. This is the entirety of the viewDidLoad method currently.
- (void)viewDidLoad
{
[super viewDidLoad];
_deck = [[Deck alloc] init];
[_deck randomize];
}
Any and all help will be appreciated. Sorry if the answer is dead obvious.
This is because you are trying to insert into an index that doesn't exist yet. You need to initialize the array with as many places in the array as you need for your cards. Either that or use a NSMutableDictionary and just insert the object with the index being the key.
To add another note, calling initWithCapacity on the array wouldn't solve this for you either since this just gives a "hint" at the size. You need the count property of the array to actually be at least as large as the index you are trying to insert. If you wanted to do an array, then you would first need to populate something in each index first. You could define this in the new array literal format or use a for loop that loops the number of times you need (your max index) and insert a dummy object in it's place.
for (int i=0; i< _cards.count; ++i)
{
[tmpDeck insertObject:#"dummy" atIndex:i];
}
Then instead of checking for 'nil' before you replace, you check if it is equal to the dummy object you inserted. This would give you an array that you can insert into any of these indexes. I personally would still probably store them in an NSMutableDictionary. But if you need it in an array for some other purpose then this is a way to do it.
You also will need to be sure to replace the object instead of inserting, otherwise you will just keep adding indexes.
[tmpDeck replaceObjectAtIndex:random withObject:tmp];
If you still get the same error, set a breakpoint in your debugger and check what the random number is and what the count of your array is. If your random number is ever greater than your array count, then you will get this error.

Cocos2d: troubles scheduling call a method multiple times at specific time intervals

I ran the following code expecting to schedule three subsequent calls, at different time intervals (e.g. after 1 sec, after 2.6sec etc..) on the method "displayWarningMessage" but didn't work (it displayed the massage only the first time).
I don't find a method signature in the scheduler that would do the job of displaying it multiple times and with a specific delay. Anyone has some suggestion?
[self scheduleOnce:#selector(displayWarningMessage) delay:0.7f];
[self scheduleOnce:#selector(displayWarningMessage) delay:1.7f];
[self scheduleOnce:#selector(displayWarningMessage) delay:3.7f];
Problem here is, when you call first schedule it is scheduled successfully. But the next immediate call is throwing warning something
CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: X.2 to X.2
you can see this in the log.
What you can do is when the selector is called, at the end of the method you can schedule it again for the next time, until you are done. You may take a counter to keep track of how many times it has been called, put all of your intervals in an array and then schedule next selector for the interval at the specific index identified by counter. like this:
NSArray *intervals = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.7],[NSNumber numberWithFloat:1.7],[NSNumber numberWithFloat:3.7], nil];
int counter = 0;
//schedule it for the first time with object at index counter/index 0
[self scheduleOnce:#selector(displayWarningMessage) delay:[(NSNumber *)[intervals objectAtIndex:counter]] floatValue];
now in your selector, do something like this:
-(void)displayWarningMessage
{
//do all your stuff here
//increment counter
counter ++;
if(counter < [intervals count])
{
//schedule it for the next time with object at index counter/index
[self scheduleOnce:#selector(displayWarningMessage) delay:[(NSNumber *)[intervals objectAtIndex:counter]] floatValue];
}
}
intervals and counter should be class ivars of-course.
Try this:
- (void)displayWarningMessage {
//Stuff
}
- (void)callStuff {
CCCallFunc *call = [CCCallFunc actionWithTarget:self selector:#selector(displayWarningMessage)];
CCDelayTime *delay1 = [CCDelayTime actionWithDuration:0.7f];
CCDelayTime *delay2 = [CCDelayTime actionWithDuration:1.7f];
CCDelayTime *delay3 = [CCDelayTime actionWithDuration:3.7f];
CCSequence *actionToRun = [CCSequence actions:delay1, call, delay2, call, delay3, call, nil];
[self runAction:actionToRun];
}
That should work for what you're trying to do, at least that's how I'd imagine doing it. I'm fairly sure you can call that CCCallFunc multiple times in one CCSequence without having to create it three individual times. You could also make those delays variable based if need be, of course. Let me know how it goes.
Method is created.
[self schedule: #selector(displayWarningMessage:) interval:3.2f];
-(void) displayWarningMessage:(ccTime) delta
{
CCLOG(#"alert........!!!!!!");
}
Use the Calling method in not warning message detected.

How do I select a random key from an NSDictionary?

When I was using an NSArray, it was easy:
NSArray *array = ...
lastIndex = INT_MAX;
...
int randomIndex;
do {
randomIndex = RANDOM_INT(0, [array count] - 1);
} while (randomIndex == lastIndex);
NSLog(#"%#", [array objectAtIndex:randomIndex]);
lastIndex = randomIndex;
I need to keep track of the lastIndex because I want the feeling of randomness. That is, I don't want to get the same element twice in a row. So it shouldn't be "true" randomness.
From what I can tell, NSDictionary doesn't have something like -objectAtIndex:. So how do I accomplish this?
You can get an array of keys with allKeys (undefined order) or keysSortedByValueUsingSelector (if you want sorting by value). One thing to keep in mind (regarding lastIndex) is that even with sorting, the same index may come to refer to a different key-value pair as the dictionary grows.
Either of these (but especially keysSortedByValueUsingSelector) will come with a performance penalty.
EDIT: Since the dictionary isn't mutable, you should just be able to call allKeys once, and then just pick random keys from that.
You could use the code below:
- (YourObjectType *)getRandomObjectFromDictionary:(NSDictionary *)dictionary
{
NSArray *keys = dictionary.allKeys;
return dictionary[keys[arc4random_uniform((int)keys.count)]];
}
To make it more efficient, you can cache keys in an instance variable. Hope this helps.

Resources