问题
I am encountering a problem with a basic content delivery app. The concept is simple in that the app should periodically load updates. The first time, there is no data to go by on the local device, so I am pulling down many files ( all of them ) during the course of an update. The update progress is reported to the user by a UIProgressView comparing expected bytes to received bytes. The problem is that everything is going well about 90% of the time, but sometimes there are collisions. The evidence of a collision is produced by the following series of traceable events: 1)Start request A. 2)Did receive data for resource A. 3) Did receive (more) data for resource A. 4) Did complete request for A. 5) Follow the same process with request B for resource B.
**Note that this happens for about 1200 total requests for small HTML pages.
Even though I log each event and make sure that resource A was completely received before request B gets triggered, in the middle, the collisions can be seen. Everything appears to load perfectly, but when you load the views that show the HTML content from the local file system, some pages have data from the other requests' responses. So if resource A just contained the phrase "The fox is red and sly." and resource B contains "The owl is grim and wise." The response for request A may end up incorrectly as "The fox is red and sly. The owl is" where the expected result would have simply been "The fox is red and sly."
I have traced it all the way to the "didReceiveData :(NSData *)data" method where the data received is corrupted, so no wonder it gets appended to the total *responseData variable which is appended to each time data is received and written to a local file only on the "didFinishLoading" method.
I have created an update manager class and a custom request class that I will include. I hope that some guru out there might have seen this before and can lend a hand in cracking this one.
// BT_updateRequest.m
#import "BT_updateRequest.h"
#import "BT_debugger.h"
#import "myProject_appDelegate.h"
@implementation BT_updateRequest
@synthesize product, resource, byteCountExpected, byteCountReceived,updateConn, theRequest, responseData, reboundAttempts;
static int REBOUND_MAX_ATTEMPTS = 5;
-(id)initWithReq :(NSURLRequest *)req{
if((self = [super init])){
self.theRequest = req;
self.updateConn = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:NO];
[self.updateConn scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; // since we are not starting immediately, we must schedule it in a run loop
self.responseData = [[NSMutableData alloc] init];
self.reboundAttempts = 0;
}
return self;
}
-(void) startConnection{
[BT_debugger showIt:self :[NSString stringWithFormat:@"Starting the request for product=%@ and resource=%@",self.product,self.resource]];
// Start the update connection's request
[self.updateConn start];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
// Get the default update manager off of the app delegate, and remove the connection from the active connections
myProject_appDelegate *appDelegate = (myProject_appDelegate *)[[UIApplication sharedApplication] delegate];
[BT_debugger showIt:self :[NSString stringWithFormat:@"FinishedLoading product=%@, resource=%@", self.product, self.resource]];
//[BT_debugger showIt:self :[NSString stringWithFormat:@"Content=%@", [NSString stringWithUTF8String:[self.responseData bytes]]]];
// Update the manager's byteCountReceived value
appDelegate.updateManager.byteCountReceived += [self.responseData length];
[appDelegate.updateManager reportProgress];
// Write the mock resource
if( self.byteCountExpected == self.byteCountReceived ){
[appDelegate writeMockResource:self.product :self.resource :(NSData *)self.responseData];
}
[appDelegate.updateManager processNextRequest];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData*)data{
[BT_debugger showIt:self :[NSString stringWithFormat:@"Did receive data for product=%@, resource=%@", self.product, self.resource]];
@synchronized( data ){
[BT_debugger showIt:self :[NSString stringWithFormat:@"Data=%@", [NSString stringWithUTF8String:[data bytes]]]];
// Update the byteCountReceived
self.byteCountReceived += [data length];
[self.responseData appendData:data];
data = nil;
}
}
-(void) connection: (NSURLConnection*)connection didFailWithError:(NSError *)error{
myProject_appDelegate *appDelegate = (myProject_appDelegate *)[[UIApplication sharedApplication] delegate];
[BT_debugger showIt:self :[NSString stringWithFormat:@"connection failed for product(%@) resource(%@); %@",self.product,self.resource,[error localizedDescription]]];
if( reboundAttempts < REBOUND_MAX_ATTEMPTS ){
// reset the connection
[self.updateConn release];
self.updateConn = [[NSURLConnection alloc] initWithRequest:self.theRequest delegate:self startImmediately:NO];
[self startConnection];
reboundAttempts++;
}else{
appDelegate.updateManager.byteCountExpected -= self.byteCountExpected;
[appDelegate.updateManager reportProgress];
}
}
-(void) dealloc{
[updateConn release];
updateConn = nil;
[responseData release];
responseData = nil;
}
@end
/* AND THE UPDATE MANAGER */ // BT_UpdateManager.m
#import "BT_UpdateManager.h"
#import "BT_updateRequest.h"
#import "myProject_appDelegate.h"
#import "BT_debugger.h"
@implementation BT_UpdateManager
@synthesize allUpdateRequests, activeUpdateRequests, byteCountExpected, byteCountReceived;
int CONCURRENT_MAX = 10;
-(id) init{
if((self = [super init])){
self.allUpdateRequests = [[NSMutableArray alloc] init];
self.activeUpdateRequests = [[NSMutableArray alloc] init];
}
return self;
}
-(void) processRequests{
[self processNextRequest];
}
-(BOOL) processNextRequest{
BOOL didProcess = FALSE;
// if there are available slots in the active update requests queue, promote the request to active
//if( [self.activeUpdateRequests count] < CONCURRENT_MAX ){
if( [allUpdateRequests count] > 0 ){
BT_updateRequest *req =(BT_updateRequest *)[allUpdateRequests lastObject];
[activeUpdateRequests addObject:req];
// Start the request
[req startConnection];
// remove the newly active object from the all updates
[allUpdateRequests removeLastObject];
didProcess = TRUE;
}
return didProcess;
}
-(void) reportProgress{
myProject_appDelegate *appDelegate = (myProject_appDelegate *)[[UIApplication sharedApplication] delegate];
float progress = 0.0f;
progress = (float)self.byteCountReceived / self.byteCountExpected;
// round to 2 decimals
progress = roundf( progress * 100.0 ) / 100.0;
//[BT_debugger showIt:self :[NSString stringWithFormat:@"progress is...%f, received=%d, expected=%d %", progress,self.byteCountReceived,self.byteCountExpected]];
[appDelegate.loadingBar setProgress:progress];
if( progress == 1.0f ){
[self finishedUpdate];
}
}
-(void) finishedUpdate{
myProject_appDelegate *appDelegate = (myProject_appDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate hideUpdateProgress];
[appDelegate setRunningLocally:TRUE];
[appDelegate launchProduct:appDelegate.activeProduct];
// reset the update manager
[self.allUpdateRequests removeAllObjects];
self.byteCountExpected = 0;
self.byteCountReceived = 0;
}
-(void) dealloc{
[allUpdateRequests release];
allUpdateRequests = nil;
[activeUpdateRequests release];
activeUpdateRequests = nil;
}
@end
回答1:
According to apple documentation (I seem to start a lot of my answers this way):
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// This method is called when the server has determined that it
// has enough information to create the NSURLResponse.
// It can be called multiple times, for example in the case of a
// redirect, so each time we reset the data.
// receivedData is an instance variable declared elsewhere.
[receivedData setLength:0];
}
来源:https://stackoverflow.com/questions/9459057/why-is-my-data-getting-corrupted-when-i-send-requests-asynchronously-in-objectiv