problem with delegate of object which is delegate of NSURLConnection - iphone

I've a class PictureDownloader for the purpose of asynchronously loading images from a server. It assigns itself as a delegate of NSURLConnection and as such, is retained by NSURLConnection. I create several of those PictureDownloader in a DetailViewController to fetch the corresponding images, so the DetailViewController is a delegate of each PictureDownloader.
When the user leaves the DetailViewController, all remaining downloads are cancelled, however sometimes it seems to be the case, that a PictureDownloader has finished loading an image (connectionDidFinishedLoading called) before the connection was cancelled, but the DetailViewController doesn't exist anymore (but the PictureDownloader does, because it's retained by NSURLConnection), so the call
[self.delegate didLoadPictureWithID:self.ID];
inside PictureDownloader will give an EXC_BAD_ACCESS or sometimes a "unrecognized selector sent to instance".
Here are the relevant parts of the source code:
creation of the PictureDownloader inside the DetailViewController
- (void)startPictureDownload:(Picture *)pic withPictureId:(NSString *)pId forID:(int)ID
{
PictureDownloader *downloader = [self.downloadsInProgress objectForKey:[NSNumber numberWithInt:ID]];
if(!downloader)
{
downloader = [[PictureDownloader alloc] init];
downloader.picture = pic;
downloader.pictureId = pId;
downloader.ID = ID;
downloader.delegate = self;
[self.downloadsInProgress setObject:downloader forKey:[NSNumber numberWithInt:ID]];
[downloader startDownload];
[downloader release];
}
}
canceling the downloads (called when the DetailViewController returns to the overview)
- (void)cancelAllDownloads
{
[self.downloadsInProgress enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
[obj cancelDownload];
}];
}
delegate method which is called when PictureDownloader finished loading
- (void)didLoadPictureWithID:(int)dID;
{
PictureDownloader *downloader = [self.downloadsInProgress objectForKey:[NSNumber numberWithInt:dID]];
if(downloader)
{
UIImageView *imageView = (UIImageView *)[self.view viewWithTag:dID];
imageView.image = [UIImage imageWithData:downloader.imageData];
[self.downloadsInProgress removeObjectForKey:[NSNumber numberWithInt:dID]];
}
}
cancelDownload method inside PictureDownloader
- (void)cancelDownload
{
[self.imageConnection cancel];
self.imageConnection = nil;
self.imageData = nil;
}
connectionDidFinishedLoading inside PictureDownloader
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if(self.picture)
{
self.picture.data = self.imageData;
NSError *error = nil;
[self.picture.managedObjectContext save:&error];
}
if(self.delegate != nil && [self.delegate respondsToSelector:#selector(didLoadPictureWithID:)] ) //place of failure
[self.delegate didLoadPictureWithID:self.ID];
self.imageData = nil;
self.imageConnection = nil;
}
Can someone give me a hint, how I can deal with this problem?
Help is much appreciated.

To avoid situations like this, I usually add a check like this at the top of connectionDidFinishLoading: and other NSURLConnection delegate methods:
if (connection != self.imageConnection) return;
As another option, you could set the delegate on each PictureDownloader to nil as you cancel it in cancelAllDownloads. Or you could set self.delegate = nil in cancelDownload.

You should check for the existence of the delegate object (and ideally the method/selector) before you attempt to make the call.
For example:
if(self.delegate && [[self.delegate] respondsToSelector:#selector(didLoadPictureWithID:)]) {
...
}
By doing this, you'll ensure that your not attempting to call a delegate that's no longer there. For more information on the respondsToSelector method, see the NSObject Protocol Reference.

When your DetailViewController goes out of scope - dealloc -, set the PictureDownloader's delegate property to nil as well.
Your issue is interesting in that the NSUrlConnection's delegate can't be set to nil in the same way. e.g. When you're PictureDownloader is de-alloced. All you can do is cancel the NSUrlConnection.
The NSURLConnection docs say the following:
Unless a NSURLConnection receives a cancel message, the delegate will receive one and only one of connectionDidFinishLoading:, or connection:didFailWithError: message, but never both. In addition, once either of messages are sent, the delegate will receive no further messages for the given NSURLConnection.
Indicating that the you can verify that the delegate will not be called back after the above messages are received.

Related

App Crash on setting UITableViewCell:textLabel:text

I am creating a split-view iPad application. When the user presses the bar button item in the master view, a modal is presented. This modal has a textfield and has an IBAction to pick up keyboard returns.
On keyboard returns, a new instance of my Farm class is created (code below). This instance is then added to an array that is stored in my delegate. I then try to reload the MasterViewController's table. Upon this reload the application crashes on cell.textLabel.text with a EXC_BAD_ACCESS error.
Farm *current = [delegate.arrayOfFarms objectAtIndex:indexPath.row];
cell.textLabel.text = [current getFarmTitle];
If I ask the array within the delegate how many elements it has, it will indeed show the current amount, even. This is what is bizarre to me about this whole thing: the Farm instances appear to be in existence.
I have instances of AppDelegate in both my MasterViewController and my NewFarmNamingView classes. The instance in the Master is to populate the table. The instance in NewFarm is to add the newly created Farm to the delegate. Code below.
Segments from class NewFarmNamingView:
- (IBAction) keyboardDonePushed:(id)sender
{
// create a Farm and add it to the delegate
NSString *text = newFarmTextField.text;
Farm *newFarm = [[Farm alloc] init];
[newFarm setFarmTitle:text];
[[delegate arrayOfFarms] addObject:newFarm];
[newFarm release];
NSLog(#"Added farm: %#" , text);
// dismiss the view
[self closeView:nil];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// initialize the delegate
delegate = [[UIApplication sharedApplication] delegate];
}
Segments from the class Farm
- (void) setFarmTitle : (NSString *) _farmTitle
{
farmTitle = _farmTitle;
}
- (NSString *) getFarmTitle
{
return farmTitle;
}
// NSCoding Methods
- (void) encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:farmTitle forKey:#"kFarmTitle"];
}
- (id) initWithCoder:(NSCoder *)aDecoder
{
farmTitle = [aDecoder decodeObjectForKey:#"kFarmTitle"];
return self;
}
// Initialization method
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
}
return self;
}
From the runtime reference: "objc_msgsend sends a message to the receiver and expects a simple return value."
I'll bet you anything that what you're returning (if you're returning anything at all) in that class method getTitleFarm is returning an incorrect value. It should be an NSString. Be absolutely sure it is returning an NSString, and not anything else.
If you need to use the respondsToSelector method to see if the class is being released, try:
if([current respondsToSelector:#selector(getFarmTitles)]) {. [current getFarmTitle];
}
else {
NSLog:(#"FAILURE!!");
}
EDIT: maybe you are not retaining or even creating this string at all. In it's initialization, wrap it in a retain]; message

Strange custom delegate actions

Ok -- this one is weird. I have a singleton class that loads information from an XML file. I am using a delegate definition as follows (I define the delegate in a separate header file to make life easier):
#protocol ResourceClassDelegate <NSObject>
#optional
- (void)picturesDidStartLoading;
- (void)picturesDidFinishLoading;
#end
In the resource file, the delegate is defined correctly (I believe):
#property (assign) id<ResourceClassDelegate> delegate;
When using the delegate, the code in the resource class is as follows:
-(void)refreshPiecesOfHistoryWithOperation {
NSLog(#"Operation Started");
if ([delegate respondsToSelector:#selector(picturesDidStartLoading)])
[delegate picturesDidStartLoading];
self.picturePacks = [HistoryXMLParser loadPicturePacks];
[self.allPiecesOfHistory removeAllObjects];
// now lets put all of them in one big file...
for (PicturePack *pp in self.picturePacks) {
for (int ct = 0; ct < [[pp piecesOfHistory] count] ; ct++) {
[self.allPiecesOfHistory addObject:(PieceOfHistory *)[[pp piecesOfHistory] objectAtIndex:ct]];
}
}
NSLog(#"Operation Ended");
if ([delegate respondsToSelector:#selector(picturesDidFinishLoading)])
[delegate picturesDidFinishLoading];
}
Now... in the class that is listening to the delegate, it is assigned:
- (void)viewDidLoad {
[super viewDidLoad];
// now for the part that makes the loading all happen...
[[ResourceClass sharedResourceClass] setDelegate:self];
}
And in the listening class, the methods are defined....
#pragma mark ResourceClassDelegate
-(void)picturesDidStartLoading {
if (loadingActivity == nil)
loadingActivity = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[self.view addSubview:loadingActivity];
[loadingActivity setCenter:[self.view center]];
[loadingActivity startAnimating];
}
-(void)picturesDidFinishLoading {
if (loadingActivity != nil) {
[loadingActivity stopAnimating];
[loadingActivity removeFromSuperview];
}
[self.tableView reloadData];
}
Now for the problem... every single time, in the listening class, the method (void)picturesDidFinishLoading is called. The method (void)picturesDidStartLoading never is called.
When I debug the code, in the resource class, the line
if ([delegate respondsToSelector:#selector(picturesDidStartLoading)])
[delegate picturesDidStartLoading];
never reaches the delegate method call - even if I remove the if statement. The line
if ([delegate respondsToSelector:#selector(picturesDidFinishLoading)])
[delegate picturesDidFinishLoading];
is always called.
any ideas?
Ok -- I figured it out....
The delegate was nil during the first call. The reason it is nil is because the function using the delegate was called in the source during the init method. The init method was not complete when the first test of the delegate was performed. At this time the delegate was nil because it is not instantiated until the the init method completes. The reason the second test of the delegate worked is because I submitted the process using an NSOperationQueue.
To fix the problem I have to move things around a bit... it's all about the timing!
Well now that was fun....
That's weird, try to remove #optional in the protocol declaration, and see if you get some warnings.
Try to print a log inside the method as well, other than that it looks fine.

iPhone - SKProductsRequest and "message sent to deallocated instance"

I got troubles implementing InAppPurchase. My implementation of purchase is in modal view controller (AppUpgradeViewController), that I present from another modal view. I do it like this:
AppUpgradeViewController * appUpgradeViewController = [[AppUpgradeViewController alloc] init];
appUpgradeViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
appUpgradeViewController.delegate = self;
[self presentModalViewController:appUpgradeViewController animated:YES];
[appUpgradeViewController release];
Then, in my upgrade view I do the following:
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
NSSet *productIdentifiers = [NSSet setWithObject:kInAppPurchaseProUpgradeProductId];
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
self.productsRequest.delegate = self;
[productsRequest start];
Then I have implemented
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
where I do:
[self.productsRequest release];
and then I have other required methods.
The problem is when I show modal, and quickly dismiss it then after few seconds i got the following on console (I turned on NSZombieEnabled):
*** -[AppUpgradeViewController respondsToSelector:]: message sent to deallocated instance 0x2e91f0
I suppose that it's something with that product request, but I don't know how to debug or fix it. It seems that the answer for request comes to this controller just after it's dismissed (and deallocated), but I don't know how to prevent it from receiving messages after dismiss/dealloc.
Thanks for any help!
I had the same problem. Not with a modal view, but with a pushed view on the navigation controller stack. When I quickly navigated back before my product information was loaded via SKProductsRequest, it also throws me an message sent to deallocated instance exception. I solved this by calling the cancel method (see reference) on my SKProductsRequest object.
Additional to this I also set the delegate to nil.
This is my product request:
NSSet *productIdentifiers = [NSSet setWithObject:#"MY_IDENTIFIER"];
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
productsRequest.delegate = self;
[productsRequest start];
and this is what I called in the dealloc method to cancel it.
productsRequest.delegate = nil;
[productsRequest cancel];
productsRequest = nil;
I also removed the observer from the SKPaymentQueue like described in this answer for another question.
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
You probably forgot to nil your request delegate in AppUpgradeViewController's dealloc:
- (void)dealloc {
...
productsRequest.delegate = nil;
[productsRequest release], productsRequest = nil;
...
[super dealloc];
}
I guess that's because you have released your productsRequest, but it seems you haven't set the pointer to nil which means it's still pointing at the now-invalid memory location.
How is the productsRequest property defined ? If it has the retain option, then instead of:
[self.productsRequest release];
you need to do:
self.productsRequest = nil; // Property will do the release for you.
If it has the assign option, then you need to do:
[self.productsRequest release];
self.productsRequest = nil; // Or else some might access this pointer,
// which now might point to nirvana.
Is it because you're doing this:
[appUpgradeViewController release];
too early?
Try doing it in the dealloc method of whatever class you're allocing it in.
Providing you're not allocing it more than once, of course. This would also require you to move your declaration into the class header.
Swift 3
It's a good idea to close the request if you started it. This is a safe way to do it in Swift.
// strong reference at top of class
var productRequest: SKProductsRequest!
// at some point you will fetch products
// on deallocating the window
if productRequest != nil {
productRequest.cancel()
productRequest.delegate = nil
}

Searching using NSOperation

I am trying to make a searchbar to search for things I receive using NSURLConnection.
right now, if I search for something, that string is send away as an URL with an asynchronous request, which gives me data.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
[theConnection cancel];
[theConnection release];
theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
That data is parsed and when it is successful I post a notification
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
xmlParser = [[NSXMLParser alloc] data];
[xmlParser setDelegate:xmlGeocoder];
BOOL success = [xmlParser parse];
if(success == YES){
NSLog(#"No Errors");
[[NSNotificationCenter defaultCenter] postNotificationName:#"getArray" object:self];
}else{
NSLog(#"Error Error Error!!!");
[[NSNotificationCenter defaultCenter] postNotificationName:#"failToGetArray" object:self];
}
}
and my searchresultsTableView is reloaded.
self.array1 = [array2 copy];
[self.searchDisplayController.searchResultsTableView reloadData];
All these methods are depending on eachother, so B can't be executed, when A is still busy.
I am using NSNotificationCenter to tell them to execute those code.
But I want to try NSOperation and I have no idea HOW to implement that.
Do I have to put my search requests in an operation or every method I'm using?
Can someone give me a sample code to give me the idea how this should be done?
Thanks in advance...
NSOperation is very useful. To use it you extend NSOperation and override the "main" method.
In the main method you do your calculations/web request etc. So NSOperation is best for tasks you can wrap into a few simple steps, after each step you test if everything is good and either continue to the next step or cancel the operation. Once this is done you can simply instantiate your custom NSOperation and hand it off to a NSOperationQueue object and it will take care of the threading, starting, stopping cleaning up etc.
In the example below I have written a protocol to handle the completion of the task, I would advise you take this approach instead of using notification - unless you have multiple objects that needs to be notified instantly.
Make a new class that extends the NSOperation class:
//This object takes a "searchTerm" and waits to be "started".
#import <Foundation/Foundation.h>
#protocol ISSearchOperationDelegate
- (void) searchDataReady:(NSArray*) searchResult;
#end
#interface ISSearchOperation : NSOperation {
id <ISSearchOperationDelegate> delegate;
NSString *searchTerm;
}
#property(nonatomic, retain) NSString *searchTerm;
#property(nonatomic, assign) id delegate;
- (id) initWithSearchTerm:(NSString*) searchString;
#end
When an object extending NSOperation is added to an NSOperationQueue, the queue object
tries to call a "main" method on the NSOperation, you must therefore wrap your task in this method.
(notice that after each completed sub-task I test if it went well and "return" if not. The NSOperation class
has a property called isCancelled This property can be set by the NSOperationQueue, so you must also
test if that has been set during your completion of main. So to recap, you test from the inside of main if each step went as you wanted and you test if something on the outside has cancelled your task.):
- (id) initWithSearchTerm:(NSString*) searchString {
if (self = [super init]) {
[self setSearchTerm:searchString];
}
return self;
}
- (void) main {
[self performSelector:#selector(timeOut) withObject:nil afterDelay:4.0];
if ([self isCancelled]) return;
NSData *resultData = [self searchWebServiceForString:self.searchTerm];
if (resultData == nil) return;
if ([self isCancelled]) return;
NSArray *result = [self parseJSONResult:resultData];
if ([self isCancelled]) return;
if (result == nil) return;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[delegate performSelectorOnMainThread:#selector(searchDataReady:) withObject:result waitUntilDone:YES];
}
//I have not copied the implementation of all the methods I call during main, but I hope you understand that they are just "tasks" that each must be successfully completed before the next sub-task can be computed.
So first of I put a timeout test in there, then I get my data from the web service and then I parse it.
Ok to get all this going you need a queue.
So in the class you want to be the delegate for this operation you do this:
somewhere set up a queue:
NSOperationQueue *q = [[NSOperationQueue alloc] init];
[self setQueue:q];
[q release];
- (void) doSearch:(NSString*) searchString {
[queue cancelAllOperations];
ISSearchOperation *searchOperation = [[ISSearchOperation alloc] initWithSearchTerm:searchString];
[searchOperation setDelegate:self];
[queue addOperation:searchOperation]; //this makes the NSOperationQueue call the main method in the NSOperation
[searchOperation release];
}
//the delegate method called from inside the NSOperation
- (void) searchDataReady:(NSArray*) results {
//Data is here!
}
Some of the advantages with NSOperations is that from the caller point of view, we simply make an object, set a delegate, wait for the reply. But behind the scenes a series of threaded tasks that can be cancelled at any time is run, and in a manner that can handle if threaded stuff fails.
As you can see in the doSearch method it starts out by canceling any previous operations, I did this in an app where I would search a web service each time a user typed a letter in a word. That means that if the user searched for "hello world" - I would do a search for "h", then "he", then "hel", then hell", then "hello" etc.
I wanted to stop and clean up the "h" task as soon as the user typed the "e", because it was then obsolete.
I found out NSOperation was the only way that gave the responsiveness of threading and none of the mess that usually comes with spawning many threads on top of each other.
Hope you can use it to get started:)

CoreData weird behavior when data are loaded on background thread

I have very strange problem, when I don't understand what's going on at all, so I'm looking for explanation of it. Situation is as following:
I have a view controller with scrollview with three subviews in it. Those three subviews have method
-(void)loadContent
which loads content from database using CoreData in the background thread, creates subviews which represent loaded items and add them as own subviews calling [self addSubview: itemView]; That method is invoked as
[self performSelectorInBackground: #selector(loadContent) withObject: nil];
To load data from DB I'm using a singleton service class. Everything worked fine, but when those three views are loading their portions of data, it sometimes crashes the app.
I guessed it's because it shares one NSManagedObjectContext instance for all read operations, so I rewrote the class so it shares only NSManagedObjectModel and NSPersistentStoreCoordinator instances and creates it's own NSManagedObjectContext instance.
Suddenly, very strange thing happened. Data are loaded ok, subviews are created and added to the view hierarchy, but it get never displayed on the screen. When I switch back to the old singleton service class (sharing one managedObjectContext), it works again like a charm! (but with risk of crashing the app, though).
I absolutely don't get the point how loading data from DB is related to displaying items on the screen. More on that - when subviews are created and added to the view hierarchy, why the hell it don't get displayed?
The source looks like this:
- (void) loadContent {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *results = [(WLDataService *)[WLDataService service] loadItemsForGDView];
NSUInteger channelPosition = 0;
CGFloat position = 0.0;
CGFloat minuteWidth = ((self.superview.frame.size.width / 2.0) / 60.0);
for(Item *it in results) {
/// On following lines size and position of the view is computed according to item setup - skipping here...
/// Create item; it's simple subclass of UIView class
WLGDItemView *item = [[WLGDItemView alloc] init];
/// Variables used here are declared above when size and position is computed
item.frame = CGRectMake(itemX, itemY, itemWidth, itemHeight);
[self performSelectorOnMainThread: #selector(addSubview:) withObject: item waitUntilDone: NO];
/// This is just helper macro to release things
WL_RELEASE_SAFELY(item);
}
[pool drain];
}
The basic service class (non-singleton one) implementation is as follows (just interesting parts):
#import "WLLocalService.h"
static NSPersistentStoreCoordinator *sharedPSC = nil;
static NSManagedObjectModel *sharedMOM = nil;
#implementation WLLocalService
#synthesize managedObjectContext;
/// This is here for backward compatibility reasons
+ (WLLocalService *) service {
return [[[self alloc] init] autorelease];
}
#pragma mark -
#pragma mark Core Data stack
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext == nil) {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
[managedObjectContext setUndoManager: nil];
[managedObjectContext setMergePolicy: NSMergeByPropertyStoreTrumpMergePolicy];
}
return managedObjectContext;
}
- (NSManagedObjectModel *) managedObjectModel {
if(sharedMOM == nil) {
sharedMOM = [[NSManagedObjectModel mergedModelFromBundles: nil] retain];
}
return sharedMOM;
}
- (NSPersistentStoreCoordinator *) persistentStoreCoordinator {
if(sharedPSC == nil) {
NSURL *storeUrl = [self dataStorePath];
NSError *error = nil;
sharedPSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![sharedPSC addPersistentStoreWithType: NSSQLiteStoreType configuration: nil URL: storeUrl options: nil error: &error]) {
WLLOG(#"%#: %#", error, [error userInfo]);
}
}
return sharedPSC;
}
#pragma mark -
#pragma mark Path to data store file
- (NSURL *) dataStorePath {
return [NSURL fileURLWithPath: [WL_DOCUMENTS_DIR() stringByAppendingPathComponent: #"/DB.sqlite"]];
}
- (void)dealloc {
WL_RELEASE_SAFELY(managedObjectModel);
[super dealloc];
}
#end
I'd really love to know what's going on here and why it behaves so strange (and - of course - why it does not work, in particular). Can anybody explain that?
thanks to all
Have you read Multi Threading with Core-Data twice?
First, do not load or construct UI elements on a background thread. The UI (whether on the desktop or on the iPhone) is single threaded and manipulating it on multiple threads is a very bad idea.
Second, data that you load into one context will not be immediately visible in another context. This is what is causing part of your problem.
The solution is to move all your UI code to the main thread and warm up the Core Data cache on a background thread. This means to load the data on a background thread (into a separate cache) to load it into the NSPersistentStoreCoordinator cache. Once that is complete your main thread can access that data very quickly because it is now in memory.
You realize that [WLDataService service] does not actually return a singleton? It creates a new instance every time. So you are effectively working with multiple instances of the Core Data components.
What about:
static WLDataService* gSharedService = NULL;
#implementation WLDataService
+ (id) service
{
#synchronized (self) {
if (gSharedService == NULL) {
gSharedService = [[self alloc] init];
}
}
return gSharedService;
}
#end
That will create the same instance every time. You will also want to make your managedObjectContext, managedObjectModel and persistentStoreCoordinator methods thread safe by using a #synchronized block. Otherwise there is a change that multiple threads will initialize those at the same time, leading to unexpected behaviour.