I have a view which has a UITableView where I am lazy loading images. I have a class called ThumbDownloader where I initialize an NSURLConnection and upon finishing loading the image when connectionDidFinishLoading is called, within connectionDidFinishLoading, I make a call like this back to my main view:
[delegate appImageDidLoad:self.indexPathInTableView];
In my main view I have an array of ThumbDownloader instances. The array is named: imageDownloadsInProgress
The problem is, if I enter the view and quickly exit it before all of the images are done downloading, I get the zombie:
-[myApp appImageDidLoad:]: message sent to deallocated instance 0xa499030
I have tried a bunch of ways to release the ThumbDownloader instances in dealloc and such, but nothing seems to work.
Here is where I set up the instance and add it to the array:
- (void)startIconDownload:(Product *)product forIndexPath:(NSIndexPath *)indexPath
{
ThumbDownloader *thumbDownloader = [imageDownloadsInProgress objectForKey:indexPath];
if (thumbDownloader == nil)
{
thumbDownloader = [[ThumbDownloader alloc] init];
thumbDownloader.product = product;
thumbDownloader.imageSizeWidth = 87;
thumbDownloader.imageSizeHeight = 87;
thumbDownloader.indexPathInTableView = indexPath;
thumbDownloader.delegate = self;
[imageDownloadsInProgress setObject:thumbDownloader forKey:indexPath];
[thumbDownloader startDownload];
[thumbDownloader release];
}
}
Make sure you clear the delegate on the NSURLConnection.
Just add
[connection cancel]
in the dealloc method of ThumbDownloader class. This will cancel the ongoing download and will prevent the message from being called.
Related
I'm making an app that pulls images from both Flickr, and Imgur using their API.
After populating my model that stores the image URL and the title of the image, I want to reload the UI so that, the images populate the collectionview, but when its called, collectionview is nil.
This delegate method is called in the class responsible for fetching images using the APIs.
-(void)refreshUIOnMainThread
{
photosFromWeb = [libraryAPI getPhotos];
if([self.delegate respondsToSelector:#selector(reloadUIAfterImageDownload:)]) {
[self.delegate reloadUIAfterImageDownload:photosFromWeb];
}
}
The delegate method is defined in ViewController.m, the class where the UICollectionView delegate functions should be called upon a call to reloadData.
-(void)reloadUIAfterImageDownload:(NSArray*)photosFromWeb
{
allPhotos = photosFromWeb;
NSLog(#"reloadUIAfterDelegate: Number of Photos in Photo Model: %lu\n",
(unsigned long)[allPhotos count]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"about to reload collectionview...\n");
//collectionview is nil, so reloadData is not called???????
[self.collectionView reloadData];
});
}
Originally I thought the photo Model array was 0, meaning 0 cells would populate.
-(NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section
{
NSLog(#"numberofItemsInSection: Number of Photos in Photo Model: %lu\n",
(unsigned long)[allPhotos count]);
return ([allPhotos count]);
}
but that's not the case.
NSLog(#"reloadUIAfterDelegate: Number of Photos in Photo Model: %lu\n",
(unsigned long)[allPhotos count]);
returns a count of 128 indicating that Photo objects are there, and in the debugger I find the collectionview has a nil value within the delegate method definition. Why could this be?
Repo: https://github.com/danielv775/Flickr-Image-Gallery/tree/master/Photo%20Gallery
These functions are in FlickrClient.m and ViewController.m
Ah I see the issue now. It lies in this line of LibraryAPI.m:
flickrClient = [[FlickrClient alloc]init];
vc = [[ViewController alloc]init];
flickrClient.delegate = vc;
You're creating a new instance of your view controller, so none of the IBOutlets are set up on this new instance. Instead, you need to set your delegate from ViewController.m like so:
- (void)viewDidLoad {
[super viewDidLoad];
LibraryAPI *libraryAPI = [LibraryAPI sharedInstance];
libraryAPI.flickrClient.delegate = self;
}
This assumes you have a flickrClient property on your LibraryAPI. You could also add a delegate property on LibraryAPI if you wanted.
NOTE: You also want to change your delegate property on flickrClient like so:
#property (weak, nonatomic) id <FlickrClientDelegate> delegate;
Delegates should not maintain strong references.
In my model, data is downloaded from website in a for loop and in a each turn data is sending to my viewController using protocol method. In model file;
for(NSString* data in DataArray){
[self.delegate passUpdatingCourse:data_name];
//other operations
}
In my viewController data name coming from model is saving to NSArray property in other thread;
ModelClass *modelObject = [[ModelClass alloc] init];
[modelObject setDelegate:self];
dispatch_queue_t otherQ = dispatch_queue_create("Q", NULL);
dispatch_async(otherQ, ^{
//other operations
[self performSelectorOnMainThread:#selector(passUpdatingCourse:) withObject:nil waitUntilDone:YES];
dispatch_async(dispatch_get_main_queue(), ^{
[self.myIndicator stopAnimating];
self.indicatorText.hidden = YES;
[self.changingCourseLabel setNeedsDisplay];
});
});
And also data coming via protocol method is setting viewController label;
-(void)passUpdatingCourse:(NSString *)data_in{
self.myLabel.text = data_in;
}
When each data came, myLabel in a viewController must be update. But it is not happens. In a protocol method when I use this;
NSLog(#"Data:%#",self.myLabel.text);
Yeah it shows data in a console but myLabel in a view is not changing.
I searched questions like that but couldn't find a solution.
Assuming your loop is on a background thread, dispatch your label updates to the main queue (async).
If you're inside a for loop on the main thread nothing is going to get updated in the UI until your method returns and dispatch won't help in that case.
I have this code :
__block NSMutableArray *subCategoriesBlock = self.subCategories ;
__block UITableView *lstSubCategoriesBlock = self.lstSubCategories;
[[AsyncRequest initRequest: onCompletedBlock:^(NSMutableArray *subcategories) {
[subCategoriesBlock addObjectsFromArray:subcategories];
[lstSubCategoriesBlock reloadData];
}]ExecuteRequest];
AsyncRequest class will send http Request on the background and then will run onCompletedBlock.
my problem:
some times the system dealloc the current view controller and then call onCompleted block, and crash on [lstSubCategoriesBlock reloadData] because it already deallocated.
what should I do , to prevent the system from executing [lstSubCategoriesBlock reloadData] when it realeasd the viewContorller ?
Use __weak so that when object is deallocated it points to nil
__block __weak NSMutableArray *subCategoriesBlock = self.subCategories ;
__block __weak UITableView *lstSubCategoriesBlock = self.lstSubCategories;
[[AsyncRequest initRequest: onCompletedBlock:^(NSMutableArray *subcategories) {
[subCategoriesBlock addObjectsFromArray:subcategories];
[lstSubCategoriesBlock reloadData];
}]ExecuteRequest];
I have the following class:
File_Downloadmanager.h:
#import "ASINetworkQueue.h"
#interface File_Downloadmanager : NSObject {
}
-(void)addRequestToDownloadQueue:(NSString*)objectID :(NSString*)userID :(NSString*)filename;
-(void)initDownloadQueue; // creates a new download queue and sets delegates
-(void)startDownload; // starts the download queue
-(void)requestFinished;
-(void)requestFailed;
-(void)queueFinished;
#property(retain) ASINetworkQueue *downloadQueue;
#end
File_Downloadmanager.m:
#implementation File_Downloadmanager
#synthesize downloadQueue;
-(void)initDownloadQueue{
NSLog(#"Init DownloadQueue");
// Stop anything already in the queue before removing it
[[self downloadQueue] cancelAllOperations];
[self setDownloadQueue:[ASINetworkQueue queue]];
[[self downloadQueue] setDelegate:self];
[[self downloadQueue] setRequestDidFinishSelector:#selector(requestFinished:)];
[[self downloadQueue] setRequestDidFailSelector:#selector(requestFailed:)];
[[self downloadQueue] setQueueDidFinishSelector:#selector(queueFinished:)];
[self downloadQueue].shouldCancelAllRequestsOnFailure = NO;
}
-(void)startDownload{
NSLog(#"DownloadQueue Go");
[downloadQueue go];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
// If no more elements are queued, release the queue
if ([[self downloadQueue] requestsCount] == 0) {
[self setDownloadQueue:nil];
}
NSLog(#"Request finished");
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
// You could release the queue here if you wanted
if ([[self downloadQueue] requestsCount] == 0) {
[self setDownloadQueue:nil];
}
//... Handle failure
NSLog(#"Request failed");
}
- (void)queueFinished:(ASINetworkQueue *)queue
{
// You could release the queue here if you wanted
if ([[self downloadQueue] requestsCount] == 0) {
[self setDownloadQueue:nil];
}
NSLog(#"Queue finished");
}
-(void)addRequestToDownloadQueue:(NSString*)objectID :(NSString*)userID :(NSString*)filename{
...SourceCode for creating the request...
// add operation to queue
[[self downloadQueue] addOperation:request];
}
In another class a function is called an inside that function I'm doing the following:
-(void)downloadFiles{
File_Downloadmanager * downloadhandler = [[File_Downloadmanager alloc]init];
// initialize download queue
[downloadhandler initDownloadQueue];
for (int i = 0; i < [meetingObjects count]; i++) {
....some other code to get the objectID, userID, etc.
[downloadhandler addRequestToDownloadQueue:ID :[loginData stringForKey:#"userId"] :[NSString stringWithFormat:#"%#%#",currentObject.id,currentObject.name]]
}
[downloadhandler startDownload];
}
Everything works fine and the download begins. But when the first file is downloaded, I get an error in the ASINetworkQueue class that my selector "requestFinished" can't be called (I don't have the exact message, can't start the app at the moment, but the failure code was exc_bad_access code=1).
Is the time of declaration / initialization of my File_Downloadmanager object the problem? Because the function "downloadFiles" is called, the DownloadManager object created, the requests added and then the "downloadFiles" method returns because the queue works async?
I haven't used the ASI networking stuff before, but have seen lots of references to it on the net.
It sounds to me like the ASINetworkQueue class expects it's delegate to conform to a specific protocol. If it's set up correctly, you should get a warning when you try to assign yourself as the delegate of the ASINetworkQueue object but have not declared that your class conforms to the appropriate protocol. If you DO include a protocol declaration, then you should get a warning that you have not implemented required methods from that protocol.
Try cleaning your project and rebuilding, and then look carefully for warnings, specifically on your line:
[[self downloadQueue] setDelegate:self];
EDIT: I just downloaded one of the ASIHTTPRequest projects, and to my dismay, the delegate property of the ASINetworkQueue class does not have to conform to a specific protocol. This is bad programming style. If you set up a delegate, you should make the delegate pointer conform to a specific protocol.
Also, be aware that the ASI networking classes have not been maintained for several years now and are getting badly out of date. There are better alternatives out there, and you should look at moving to a different networking framework.
It looks like the downloadhandler object that ASINetworkQueue is attempting to send the requestFinished message to no longer exists at the time that the message is sent to it, as it's probably being deallocated when the downloadFiles method finishes executing. Instead of making the downloadhandler object local to the downloadFiles method, instead make it a (strong, nonatomic) property within the class that contains the downloadFiles method. That way, you can ensure that it will still exist when requestFinished is called.
I am very new to IOS programing.
I've created a class, that uses a NSURLConnection to download data async.
I use a delegate that's sent across and in turn updates my father class. Part of updating the father class includes calling a UIView that has been saved in a local property.
Here are some code examples of what I mean:
myClass:
#synthesize myView = _myView;
-(void) loadMetaData
{
if(self.isMetadataLoaded)
{
[self.myView metadataLoaded];
}
else {
[_htmlLinter summarizeUrl:self.originalLink];
}
}
-(void) urlSummarized:(NSDictionary*)data
{
self.productTitle = [data objectForKey:#"title"];
self.productDescription = [data objectForKey:#"description"];
self.provider = [data objectForKey:#"provider_name"];
self.isMetadataLoaded= true;
[self.myView metadataLoaded];
}
htmlLinter:
-(void)summarizeUrl:(NSString*)url
{
NSURL* u = [[NSURL alloc] initWithString:request];
...
...
...
//removed a lot of logic that doesn't seem to be relevant
//Important to notice, that this is being called on a different thread though:
[self embedlyDidLoad:result];
}
-(void) embedlyDidLoad:(id)result
{
NSDictionary* properties = (NSDictionary*)result;
[_facilitator urlSummarized:properties];
}
The strange thing is this:
myClass doesn't remember what self.myView is when accessed through another thread.
This line the problematic one : [self.myView metadataLoaded];
it returns a valid pointer when called initially in loadMetaData,
but when I call it on another thread in urlSummarized, it is nil.
What can be causing that?