iOS5 crashes during runMode:beforeDate:
I have a problem with compatibility of my application with an iOS5 b7 and GM versions.
The issue occurs in the next lines of code:
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
App crashes with signa开发者_如何学Gol EXC_BAD_ACCESS
after some iterations.
The number of passed iterations is random (from 2 till 7).
Also everything works quite well on iOS4 and iOS3.
The same issue occurs in Apple's sample: XMLPerformance Sample.
What do you think about this?
October 12th thousands of users of my app will upgrade to iOS5 and I don't want my app to be with such a strange error in the AppStore.
4 hours passed and I've found the problem. I will describe how I've resolved the problem in XMLPerformance sample
.
The problem was in NSAutoreleasePool
. There is @property (nonatomic, assign) NSAutoreleasePool *downloadAndParsePool;
. When the app starts to download Top300 Paid Apps RSS
new thread is created using [NSThread detachNewThreadSelector:@selector(downloadAndParse:) toTarget:self withObject:url];
. So in that thread we should keep local autorelease pool. It is done in next way:
- (void)downloadAndParse:(NSURL *)url {
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
// initializing internet connection and libxml parser.
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
[downloadAndParsePool release];
self.downloadAndParsePool = nil;
}
So in downloadAndParse:
everything looks fine. Now let's look in one method that is called when an item from RSS is parsed:
- (void)finishedCurrentSong {
// sending new item to delegate and other ...
countOfParsedSongs++;
// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but
// taking this action too frequently would be wasteful and reduce performance.
if (countOfParsedSongs == kAutoreleasePoolPurgeFrequency) {
[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
countOfParsedSongs = 0;
}
}
As you see there lines :
[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
So exactly that lines causes the exception. If I comment them everything works great.
But I decided not only to comment that lines but also replace NSAutoreleasePool
in - (void)downloadAndParse:(NSURL *)url
with @autorelease
block as it is said that it is more efficient:
- (void)downloadAndParse:(NSURL *)url {
@autoreleasepool {
// initializing internet connection and libxml parser.
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
}
}
Now everything works fine. The only problem that I haven't resolved is:
// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but
// taking this action too frequently would be wasteful and reduce performance.
So if anybody has any thoughts about this problem can post another one answer and may be try to explain more correctly the bug fix. I will be glad to accept that answer.
Thanks.
This looks like memory problem, please check Apple Technote QA1367 "Finding EXC_BAD_ACCESS bugs in a Cocoa project"
In your code, try this to crash as soon as possible:
[item release], item = nil;
It doesn't solve the problem, just makes the crash happen earlier and hopefully give you a more meaningful callstack to study.
If you're using multi-threading, well... You could try to print "current" thread id into console to verify that everything really is run in thread where you expect them to be running. Especially verify that all UI stuff is in main thread, even when such code is run as side-effect of other code (error popups, maybe).
#include <pthread.h>
- (void)myFunction
{
NSLog(@"Thread (%d)",
pthread_mach_thread_np(pthread_self()));
}
Run your app with Instruments, make sure to change memory verification to happen every 1 or 2 seconds. Slow, but yet again you want to get notified as close to the actual memory problem as possible.
Looking at your code: where did that "done" variable come from and who changes it's value and when? Now it looks pretty magical.
Also you could check the return value of runMode:beforeDate to make sure it was run. If the return value is NO, runLoop was not run at all. Maybe some other part of your code cannot handle such case?
Just my little contribution.
As I've got the same problem, I've discover that in iOS5, you don't need to have your own NSAutoreleasePool in a thread (used by performSelectorOnMainThread).
Then, in your code (a xml parser- same as me), I think you have to separate code from iOS4 and iOS5.
With iOS4, you need NSAutoreleasePool, but not with iOS5.
精彩评论