I'm just starting out on my first 'real' cocoa app. Note that this is Mac, not iPhone.
This function is called when my async download of onemanga finishes, and is intended to parse an NSArray list of manga from it. I get a NSArray from nodesForXPath, and retain it to make sure it's mine. (Also tried retaining it twice, heh). Unfortunately, trying to retrieve the count of the NSArray causes an EXC_BAD_ACCESS. The point of crashing is marked by two comments below.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Download Succeeded! Received %d bytes of data. Beginning Parse.",[mangaListData length]);
NSString* theQuery = [[NSString alloc]initWithString:#"//tr/td[#class=\"ch-subject\"]/text()"];
NSError *XMLError=nil;
NSError *XPathError=nil;
NSString* mangaListHTML;
NSString* fixedMangaListHTML;
mangaListHTML = [[NSString alloc] initWithData:mangaListData encoding:NSUTF8StringEncoding];
fixedMangaListHTML = [mangaListHTML stringByReplacingOccurrencesOfString:#" & " withString:#" & "];
NSXMLDocument* xmlDoc = [[NSXMLDocument alloc] initWithXMLString:fixedMangaListHTML
options:(NSXMLNodePreserveWhitespace|NSXMLNodePreserveCDATA)
error:&XMLError];
if (XMLError) {
NSLog(#"XML Parse error: %#", XMLError);
return;
};
[fixedMangaListHTML release];
[mangaListHTML release];
NSArray* results = [[xmlDoc nodesForXPath:theQuery error:&XPathError] retain];
if (XMLError) {
NSLog(#"Parse error: %#", XPathError);
return;
};
NSLog(#"Parsing complete. Manga List = ");
//CRASH HAPPENS HERE
NSLog(#"Size of array: %#", [results count]);
//CRASH HAPPENS ABOVE
for(NSXMLNode* title in results){
NSLog(#"%#\n", title.description);
};
[XMLError release];
[XPathError release];
[connection release];
[mangaListData release];
}
Heres the output of where in gdb. (I don't really know how to use gdb yet, so any commands I could run to get more info here would be highly appreciated.)
#0 0x00007fff855691d1 in objc_msgSend_vtable5 ()
#1 0x00007fff87e97207 in _NSDescriptionWithLocaleFunc ()
#2 0x00007fff8509ba2d in _CFStringAppendFormatAndArgumentsAux ()
#3 0x00007fff8509aead in _CFStringCreateWithFormatAndArgumentsAux ()
#4 0x00007fff85119f5f in _CFLogvEx ()
#5 0x00007fff87ef937f in NSLogv ()
#6 0x00007fff87ef9317 in NSLog ()
#7 0x0000000100002bca in -[TMMoneManga connectionDidFinishLoading:] (self=0x1001999f0, _cmd=0x7fff8803b5de, connection=0x1001689a0) at /Users/ripdog/Documents/TheMangaMachine/TMMoneManga.m:120
#8 0x00007fff87f16b8c in _NSURLConnectionDidFinishLoading ()
#9 0x00007fff8063f18e in URLConnectionClient::_clientDidFinishLoading ()
#10 0x00007fff806a4502 in URLConnectionClient::ClientConnectionEventQueue::processAllEventsAndConsumePayload ()
#11 0x00007fff8062b8fb in URLConnectionClient::processEvents ()
#12 0x00007fff8062b6d8 in MultiplexerSource::perform ()
#13 0x00007fff850b6f21 in __CFRunLoopDoSources0 ()
#14 0x00007fff850b5119 in __CFRunLoopRun ()
#15 0x00007fff850b48df in CFRunLoopRunSpecific ()
#16 0x00007fff80b83ada in RunCurrentEventLoopInMode ()
#17 0x00007fff80b838df in ReceiveNextEventCommon ()
#18 0x00007fff80b83798 in BlockUntilNextEventMatchingListInMode ()
#19 0x00007fff8845fa2a in _DPSNextEvent ()
#20 0x00007fff8845f379 in -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] ()
#21 0x00007fff8842505b in -[NSApplication run] ()
#22 0x00007fff8841dd7c in NSApplicationMain ()
#23 0x00000001000017cd in main (argc=1, argv=0x7fff5fbff6c0) at /Users/ripdog/Documents/TheMangaMachine/main.m:13
Thanks a lot in advance.
The problem is with your format string. The %# specifier will display an Objective-C object, but [result count] is an integer. The correct format specifier for an integer is %d.
For reference, here is a complete list of Objective-C string format specifiers.
Your question indicated that the crash happens when you try to retrieve [result count]. However if you look at the call stack, your code is frame #7, and the next function on the stack (frame #6) is NSLog. So this tells you the problem is probably something to do with the call to NSLog, and not [result count]. [result count] will already have returned before control flow enters NSLog.
There are various techniques for debugging with gdb. Since you are debugging a crash, your focus will mostly be on examining the call stack, variables and memory (as opposed to stepping through your code as it executes.) So for this kind of debugging, these commands are essential:
up This moves the "focus" of the debugger up one frame of the call stack. Let's say you want to examine the variables in your program. You can't see them in objc_msgSend_vtable5 which is where the debugger is stopped. So use up 7 to jump up seven frames so you're looking at your code.
down Is the opposite of up. Oftentimes you want to look at what led to disaster, so jumping up to your code and then going down, down, down is a way to get an understanding of how disaster unfolded. (This isn't moving you forward and backward in time, of course.)
where Shows you part of the call stack. You already discovered this. My only tip is that you often only care about what's on the top of the call stack. You can use where n to print the first n frames of the call stack.
list Shows you the source code of the "focussed" frame, with an arrow indicating the execution point. So, for example, up 7 followed by list should show you the source of connectionDidFinishLoading with an indicator next to the call to NSLog. Using list is often more convenient than finding the code in XCode, if you just need to see a few lines of the surrounding context.
print Evaluates an expression and prints it. For example, you could do print [results count] to see what [results count] evaluates to. When the debugger is stopped with your program is in a bad state, print can act funny (it can really run code.) So often simple expressions work better.
info Info can tell you lots of things, but info locals is really valuable. It shows you the local variables that are in scope in the "focussed" frame of the debugger.
count returns an integer, not an object. Print it with %d not %#.
(If you do the latter, you implicitly convert the integer to a pointer to a non-existent object in the wilds of who knows where, and then invoke its description method.)
Related
I'm trying to get a better understanding of queues and how they work. This snippet is to test their behaviour:
- (void)dispatchQueueTest
{
NSLog( #"Begin test on %# thread", [NSThread isMainThread] ? #"main" : #"other" );
dispatch_semaphore_t s = dispatch_semaphore_create(0);
dispatch_async( dispatch_get_main_queue(), ^{
NSLog( #"Signalling semaphore" );
dispatch_semaphore_signal(s);
});
NSLog( #"Waiting for worker" );
while( dispatch_semaphore_wait( s, DISPATCH_TIME_NOW ) ) {
NSDate* timeout = [NSDate dateWithTimeIntervalSinceNow:10.f];
// Process events on the run loop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout];
}
dispatch_release(s);
NSLog( #"All sync'd up" );
}
As would be expected it produces this in the log:
Begin test on main thread
Waiting for worker
Signalling semaphore
All sync'd up
What is strange is that if this code is called from, say, - (void)viewDidAppear:(BOOL)animated of a UIViewController, then it changes behaviour. Specifically it deadlocks with the following log:
Begin test on main thread
Waiting for worker
My question is why does NSRunLoop runMode not process the block sent via dispatch_async in this situation but it does in others?
I have a project where I push a PlayerNameEntryViewController onto a navigation controller. I put a breakpoint in -[PlayerNameEntryViewController viewDidAppear:]. Here's the stack trace when the breakpoint was hit:
#0 0x0002d3d3 in -[PlayerNameEntryViewController viewDidAppear:] at /Volumes/b/Users/mayoff/t/hotseat2/hotseat2/Home/PlayerNameEntryViewController.m:39
#1 0x00638fbf in -[UIViewController _setViewAppearState:isAnimating:] ()
#2 0x006392d4 in -[UIViewController __viewDidAppear:] ()
#3 0x006395d7 in -[UIViewController _endAppearanceTransition:] ()
#4 0x00648666 in -[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:] ()
#5 0x007ee90e in -[UINavigationTransitionView _notifyDelegateTransitionDidStopWithContext:] ()
#6 0x007eec17 in -[UINavigationTransitionView _cleanupTransition] ()
#7 0x007eec86 in -[UINavigationTransitionView _navigationTransitionDidStop] ()
#8 0x005a2499 in -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] ()
#9 0x005a2584 in -[UIViewAnimationState animationDidStop:finished:] ()
#10 0x00497e00 in CA::Layer::run_animation_callbacks(void*) ()
#11 0x02e86515 in _dispatch_main_queue_callback_4CF ()
#12 0x015fe833 in __CFRunLoopRun ()
#13 0x015fddb4 in CFRunLoopRunSpecific ()
#14 0x015fdccb in CFRunLoopRunInMode ()
#15 0x01acd879 in GSEventRunModal ()
#16 0x01acd93e in GSEventRun ()
#17 0x00571a9b in UIApplicationMain ()
#18 0x00002461 in main at /Volumes/b/Users/mayoff/t/hotseat2/hotseat2/main.m:17
Notice frame #11. It's a call to _dispatch_main_queue_callback_4CF. That is the function that runs blocks put on the main queue. So viewDidAppear: was actually called from inside a block that was added to the main queue with dispatch_async.
The main queue is a serial queue. The definition of a serial queue is a queue that only executes one block at a time. If the queue is executing a block, no other block on that queue can start. So when you run the main run loop recursively, the run loop sees that it's already inside a block running on the main queue and doesn't try to start more blocks. That's why your semaphore-signaling block never runs and your app hangs.
Note that sometimes viewDidAppear: is called from inside a queued block, and sometimes it's not. You shouldn't rely on either behavior.
I'm rather confused by this stack trace (only the confusing part is shown):
-[NSXMLDocument length]: unrecognized selector sent to instance 0x10187e010
An uncaught exception was raised
-[NSXMLDocument length]: unrecognized selector sent to instance 0x10187e010
(
0 CoreFoundation 0x00007fff8f5d6286 __exceptionPreprocess + 198
1 libobjc.A.dylib 0x00007fff9213bd5e objc_exception_throw + 43
2 CoreFoundation 0x00007fff8f6624ce -[NSObject doesNotRecognizeSelector:] + 190
3 CoreFoundation 0x00007fff8f5c3133 ___forwarding___ + 371
4 CoreFoundation 0x00007fff8f5c2f48 _CF_forwarding_prep_0 + 232
5 CoreFoundation 0x00007fff8f548c66 CFDataGetLength + 118
6 CoreFoundation 0x00007fff8f5791df CFStringCreateFromExternalRepresentation + 31
7 asLJ 0x0000000100013828 +[stripHTML stripAllHtmlFromString:] + 212
In particular, I don't understand where the call to CFStringCreateFromExternalRepresentation is happening, so I don't know what part of my code (+[stripHTML stripAllHtmlFromString:]) is causing the exception. What's causing the call to CFStringCreateFromExternalRepresentation? If it's obvious, what is it that I'm doing wrong that's causing the exception? In the future, how can I go about determining what's calling CFStringCreateFromExternalRepresentation?
Here's +[stripHTML stripAllHtmlFromString:]:
+ (NSString *)stripAllHtmlFromString:(NSString *)inputString
{
// based on code from http://sugarmaplesoftware.com/25/strip-html-tags/#comment-71
NSError *theError = NULL;
NSString *modifiedInputString = [NSString stringWithFormat:#"%#\n\n\n\n\n\n\n\n\n\n\n\n",inputString]; // adding some spare newlines at the end to ensure that things will work even with a short non-HTML string
NSXMLDocument *theDocument = [[NSXMLDocument alloc] initWithXMLString:modifiedInputString
options:NSXMLDocumentTidyHTML
error:&theError];
NSString *theXSLTString = #"<?xml version='1.0' encoding='utf-8'?>"
"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:xhtml='http://www.w3.org/1999/xhtml'>"
"<xsl:output method='text'/>"
"<xsl:template match='xhtml:head'></xsl:template>"
"<xsl:template match='xhtml:script'></xsl:template>"
"</xsl:stylesheet>";
NSData *theData = [theDocument objectByApplyingXSLTString:theXSLTString arguments:NULL error:&theError];
[theDocument release];
return [[[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding] autorelease];
}
Oh, actually, probably -objectByApplyingXSLTString:arguments:error: returned an NSXMLDocument and not an NSData. So, the call to -[NSString initWithData:encoding:] is invoking -length on what it thinks is an NSData, but NSXMLDocument doesn't recognize that.
Ken Thomases's answer seems to have it exactly right—for some reason, for very short input (particularly the empty string), -objectByApplyingXSLTString:arguments:error: with the given XSLT returns an NSXMLDocument (even though I don't think it should). To fix it, I first detect whether we got an NSXMLDocument and if so, turn it into a string representation of the XML and feed that back into the method; otherwise assume we got the NSData that we'd originally expected.
Replacing the last 3 lines of the given method (from NSData *theData =... on) with the code below seems to have fixed the issue.
// Had a report of an exception that seemed to indicate objectByApplyingXSLTString:arguments:error: was returning an NSXMLDocument objectinstead of an NSData object, so let's guard against that. (discussed at https://stackoverflow.com/q/10669479/291280 )
NSObject *XSTLresult = [theDocument objectByApplyingXSLTString:theXSLTString arguments:NULL error:&theError];
[theDocument release];
if ([XSTLresult isKindOfClass:[NSXMLDocument class]]) {
// If the result is an NSXMLDocument, call XMLData to get an NSData object, turn it into a string, and feed that back into this method...
return [self stripAllHtmlFromString:[[[NSString alloc]
initWithData:[(NSXMLDocument *)XSTLresult XMLData]
encoding:NSUTF8StringEncoding]
autorelease]
];
} else {
// Otherwise, assume we have an NSData object.
return [[[NSString alloc] initWithData:(NSData *)XSTLresult encoding:NSUTF8StringEncoding] autorelease];
}
I'm running into an issue with loadable bundles and KVO. It seems that any class that has KVO observers attached to an instance of it cannot be safely be unloaded via NSBundle's -unload method.
I'm doing the following:
for (int i = 0; i < 100; i++)
{
[bundle load];
Class bundleClass = [bundle principalClass];
[[[bundleClass alloc] init] release];
[bundle unload];
}
And in the bundle's principle class -init method,
[self addObserver: self
forKeyPath: #"name"
options: 0
context: nil];
self.name = #"jim";
The loop gets through a number of iterations, sometimes crashing on the second time round, sometimes on the thirtieth.
It always crashes with this backtrace, with the EXC_BAD_ACCESS signal.
#0 0x00007fff8a30deab in objc_msgSend ()
#1 0x00007fff8609d862 in NSKeyValueNotifyObserver ()
#2 0x00007fff860be99b in NSKeyValueDidChange ()
#3 0x00007fff8606b0fb in -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] ()
#4 0x00000001007a4c2c in -[Bundle init] (self=0x101902130, _cmd=0x7fff8ea369b0) at /Users/joerick/Desktop/bundleTest/testbundle/Bundle.m:26
#5 0x0000000100001731 in -[SIAppDelegate applicationDidFinishLaunching:] (self=0x100326a90, _cmd=0x7fff876e285f, aNotification=0x100131ea0) at /Users/joerick/Desktop/bundleTest/bundleTest/SIAppDelegate.m:28
#6 0x00007fff8606ade2 in __-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke_1 ()
#7 0x00007fff8b470e0a in _CFXNotificationPost ()
#8 0x00007fff86057097 in -[NSNotificationCenter postNotificationName:object:userInfo:] ()
#9 0x00007fff8e1bbaa7 in -[NSApplication _postDidFinishNotification] ()
#10 0x00007fff8e1bb80d in -[NSApplication _sendFinishLaunchingNotification] ()
....
Full code here
You can download a sample project showing this issue here.
I'm thinking that this is a bug in Cocoa, but I wonder if anybody could see if I'm doing anything stupid here?
I've reported this to Apple.
rdar://11017946
I am having problem releasing NSMutableURLRequest object.
Application crashes during the deallocation of request object.
The object is created via [[NSMutableURLRequest alloc] initWithURL:my_http_url];
As mainstream flow of control, I try to release the connection object in response to connectionDidFinishLoading handler being invoked.
At first I tried autoreleasing NSMutableURLRequest right inside connectionDidFinishLoading handler. This caused crashing, so I assumed it might be because connection class pushes autorelease pool internally before calling connectionDidFinishLoading and still expects connection object to be valid when the handler returns, so it is impossible then to release or autorelease the connection object inside connectionDidFinishLoading.
If I do not release NSMutableURLRequest at all, according to Instruments it leaks with reference count 1.
Therefore I decided to do a delayed release via firing an NSRunLoop event that autoreleases NSMutableURLRequest passed to it.
This still causes a crash.
retainCount before calling autorelease is 1.
Crash stack is:
#
0 0x9448aedb in objc_msgSend
#1 0x04a47ce0 in ??
#2 0x02e51501 in HTTPMessage::~HTTPMessage
#3 0x02945621 in _CFRelease
#4 0x02e516a9 in HTTPRequest::~HTTPRequest
#5 0x02e50967 in URLRequest::~URLRequest
#6 0x02945621 in _CFRelease
#7 0x0032fb70 in -[NSURLRequestInternal dealloc]
#8 0x0032fb1a in -[NSURLRequest dealloc]
#9 0x002f27a5 in NSPopAutoreleasePool
#10 0x003b5dd0 in __NSFirePerformTimer
#11 0x0299e8a2 in __CFRunLoopDoObservers
#12 0x0296a39e in CFRunLoopRunSpecific
#13 0x0296a048 in CFRunLoopRunInMode
#14 0x031d289d in GSEventRunModal
#15 0x031d2962 in GSEventRun
#16 0x0058ede1 in UIApplicationMain
#17 0x00002b9c in main at main.m:14
Thanks for advise.
After using NSDebugEnabled/NSZombieEnabled/NSAutoreleaseFreedObjectCheckEnabled I identified the problem:
result of
http_url = [NSURL URLWithString:...]
should have been either retained or not autoreleased on its own.
Try to instantiate it as an autoreleased object using:
[NSMutableURLRequest requestWithURL:my_http_url];
I believe Instruments are not useful for any tracing in this case since the
application crashes and then is auto-restarted (it's iPad/iPhone app), so all
Intruments history is gone.
I do not see much point instantiating NSMutableURLRequest as autoreleased object
since it needs to be retained for the duration of async call spanning multiple
idle loop cycles. NSURLConnection may retain it internally or not, but keeping
an extra reference for safety should not hurt.
The initialization code boils down to essentially the following:
rq->url_encoded = [url_encode(rq->url) retain];
rq->http_url = [NSURL URLWithString:rq->url_encoded];
rq->http_request = [[NSMutableURLRequest alloc] initWithURL:rq->http_url];
[rq->http_request setHTTPMethod:#"GET"];
NSArray* availableCookies = [rq->service.CookieJar cookiesForURL:rq->http_url];
NSDictionary* headers = [NSHTTPCookie
requestHeaderFieldsWithCookies:availableCookies];
[rq->http_request setAllHTTPHeaderFields:headers];
rq->http_connection = [NSURLConnection alloc];
[rq->http_connection initWithRequest:rq->http_request delegate:rq
startImmediately:YES];
The release procedure is that connectionDidFinishLoading calls [rq->http_connection
cancel] and [rq autorelease], the latter eventually leading to:
// [http_request autorelease];
// delayedAutoRelease(http_request);
[http_connection autorelease];
[http_url autorelease];
[url release];
[url_encoded release];
[response release];
Note that [response release] just pairs previous [response retain] executed inside didReceiveResponse.
If I leave first two lines commented out, NSURLRequest leaks with reference
count 1 per Instruments, and this is the only leak observed.
Whenever I attempt to autorelease http_request whichever way as described above,
a crash occurs.
I'm trying to remove annotaenter code heretion some a MKMapView but am crashing on this line:
[self.mapView removeAnnotation:p];
p is an object that implements the MKAnnotation protocol and already has an annotation on that map.
This is the message I'm getting when I crash:
objc[46534]: FREED(id): message release sent to freed object=0x4319640
Program received signal: “EXC_BAD_INSTRUCTION”.
(gdb)
The backtrace looks like so:
(gdb) bt
#0 0x951424b4 in _objc_error ()
#1 0x951424ea in __objc_error ()
#2 0x951407dc in _freedHandler ()
#3 0x000786f6 in -[NSConcreteMapTable removeObjectForKey:] ()
#4 0x00003970 in -[MapViewController horizontalPickerVC:toggleGroup:enabled:] (self=0x4322a00, _cmd=0x24349, picker=0x43248c0, groupId=3, enabled=0 '\0') at /Users/me/Desktop/FanMapper/fanmapper/Classes/MapViewController.m:183
Any ideas?
Turns out my MKPinAnnotationView was set to autorelease.