Calls to NSURLConnectionDataDelegate methods to download images only works occasionally

泄露秘密 提交于 2019-12-24 14:23:55

问题


I currently have two UIImageViews in my storyboard, one of which downloads my own Facebook profile picture, and the other a friend's profile picture.

However, my issue is that only 60% of the time this works as expected, while the other 40% of the times my own profile picture appears where my friend's picture should show in the bottom, while the top box remains empty. I am unsure whether this is the result of how I call the NSURLConnectionDataDelegate methods as it downloads or completes view, or the nature of my requests called to Facebook.

I've pasted the condensed version of my two requests to Facebook in viewDidLoad, one which gets my own profile pic, and the other to get my friend's pic:

// ----- Gets my own profile picture, using requestForMe -----
FBRequest *request = [FBRequest requestForMe];
[request startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
    //handle response
    if(!error){
        //Some methods not included for breveity, includes facebookID used below
        NSURL *pictureURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://graph.facebook.com/%@/picture?type=large&return_ssl_resources=1", facebookID]];
        self.imageData = [[NSMutableData alloc] init];
        switcher = 1;
        NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:pictureURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:2.0f];
        NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
        if (!urlConnection){
            NSLog(@"Failed to download picture");
        }
    }
}];
// ----- Gets a profile picture of my friend, using requestForMyFriends -----
FBRequest *requestForFriends = [FBRequest requestForMyFriends]; 
[requestForFriends startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
    if(!error){
        //Other methods not included, including facebookFriendID
        NSURL *friendPictureURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://graph.facebook.com/%@/picture?type=large&return_ssl_resources=1", facebookFriendID]];
        self.supportImageData = [[NSMutableData alloc] init];
        switcher = 2;
        NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:friendPictureURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:2.0f];
        NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
        if (!urlConnection){
            NSLog(@"Failed to download picture");
        }
    }
}];

Both requests make calls to the NSURLConnectionDataDelegate methods, and I use the switcher to decide when to load which picture:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    // As chuncks of the image are received, we build our data file

    if (switcher == 1) [self.imageData appendData:data];
    if (switcher == 2)[self.supportImageData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{

    //When the entire image is finished downloading
    if (switcher == 1) {
        UIImage *image = [UIImage imageWithData:self.imageData]; //puts the completed picture into the UI
        self.titleImageView.image = image;
        [self.titleImageView setClipsToBounds:YES];
    }

    if (switcher == 2) {
        UIImage *supportImage = [UIImage imageWithData:self.supportImageData];
        self.supportImageView.image = supportImage;
        [self.titleImageView setClipsToBounds:YES];
    }
}

回答1:


You have two asynchronous processes, both of which can result in having your NSURLConnectionDataDelegate methods in self being called. But if they happen at the same time, they're going to step on top of each other (you're presumably using a single NSMutableData variable to reference the data being downloaded).

Either create dedicated class that you can instantiate once for each of your two NSURLConnection requests (a NSOperation based approach, like AFNetworking, is ideal), or use sendAsynchronousRequest instead. But don't use a single object as the delegate for two concurrent NSURLConnection requests at the same time.


If you wanted to see a minimalist download operation, it might look like:

//  NetworkOperation.h

#import <Foundation/Foundation.h>

typedef void(^DownloadCompletion)(NSData *data, NSError *error);

@interface NetworkOperation : NSOperation

- (id)initWithURL:(NSURL *)url completion:(DownloadCompletion)completionBlock;

@property (nonatomic, copy) NSURL *url;
@property (nonatomic, copy) DownloadCompletion downloadCompletionBlock;

@end

and

//  NetworkOperation.m

#import "NetworkOperation.h"

@interface NetworkOperation () <NSURLConnectionDataDelegate>

@property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
@property (nonatomic, readwrite, getter = isFinished)  BOOL finished;
@property (nonatomic, strong) NSMutableData *data;

@end

@implementation NetworkOperation

@synthesize finished  = _finished;
@synthesize executing = _executing;

- (id)initWithURL:(NSURL *)url completion:(DownloadCompletion)downloadCompletionBlock
{
    self = [super init];
    if (self) {
        self.url = url;
        self.downloadCompletionBlock = downloadCompletionBlock;

        _executing = NO;
        _finished = NO;
    }
    return self;
}

#pragma mark - NSOperation related stuff

- (void)start
{
    if ([self isCancelled]) {
        self.finished = YES;
        return;
    }

    self.executing = YES;

    NSURLRequest *request = [NSURLRequest requestWithURL:self.url];
    NSAssert(request, @"%s: requestWithURL failed for URL '%@'", __FUNCTION__, [self.url absoluteString]);
    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
    [connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [connection start];
}

- (void)setExecuting:(BOOL)executing
{
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

- (void)setFinished:(BOOL)finished
{
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}

- (BOOL)isConcurrent
{
    return YES;
}

#pragma mark NSURLConnectionDataDelegate methods

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    self.data = [NSMutableData data];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    if ([self isCancelled]) {
        [connection cancel];
        self.executing = NO;
        self.finished = YES;
        return;
    }

    [self.data appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    if (self.downloadCompletionBlock) {
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.downloadCompletionBlock(self.data, nil);
            self.downloadCompletionBlock = nil;
        }];
    }

    self.executing = NO;
    self.finished = YES;
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    if (self.downloadCompletionBlock) {
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.downloadCompletionBlock(nil, error);
            self.downloadCompletionBlock = nil;
        }];
    }

    self.executing = NO;
    self.finished = YES;
}

@end

And then, when you wanted to use it, it might look like:

NSOperationQueue *networkQueue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;

// ----- Gets my own profile picture, using requestForMe -----
FBRequest *request = [FBRequest requestForMe];
[request startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
    //handle response
    if(!error) {
        //Some methods not included for breveity, includes facebookID used below
        NSURL *pictureURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://graph.facebook.com/%@/picture?type=large&return_ssl_resources=1", facebookID]];

        [networkQueue addOperation:[[NetworkOperation alloc] requestWithURL:pictureURL completion:^(NSData *data, NSError *error) {
            if (!error) {
                self.meImageView.image = [UIImage imageWithData:data];
            }
        }]];
    }
}];
// ----- Gets a profile picture of my friend, using requestForMyFriends -----
FBRequest *requestForFriends = [FBRequest requestForMyFriends]; 
[requestForFriends startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
    if(!error){
        //Other methods not included, including facebookFriendID
        NSURL *friendPictureURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://graph.facebook.com/%@/picture?type=large&return_ssl_resources=1", facebookFriendID]];

        [networkQueue addOperation:[[NetworkOperation alloc] requestWithURL:friendPictureURL completion:^(NSData *data, NSError *error) {
            if (!error) {
                self.friendImageView.image = [UIImage imageWithData:data];
            }
        }]];
    }
}];


来源:https://stackoverflow.com/questions/18173669/calls-to-nsurlconnectiondatadelegate-methods-to-download-images-only-works-occas

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!