У меня возникла проблема с основным приложением для доставки контента. Концепция проста в том, что приложение должно периодически загружать обновления. В первый раз данные на локальном устройстве отсутствуют, поэтому в процессе обновления я извлекаю много файлов (всех). Ход обновления сообщается пользователю с помощью UIProgressView, сравнивающего ожидаемые байты с полученными байтами. Проблема в том, что все идет хорошо примерно в 90% случаев, но иногда случаются столкновения. Свидетельство коллизии производится следующими сериями отслеживаемых событий: 1) Запрос на запуск A. 2) Получил ли данные для ресурса A. 3) Получил (больше) данные для ресурса A. 4) Выполнил запрос на A. 5) Выполните тот же процесс с запросом B для ресурса B.
** Обратите внимание, что это происходит приблизительно для 1200 запросов на небольшие HTML-страницы.
Несмотря на то, что я регистрирую каждое событие и проверяю, что ресурс A был полностью получен до того, как запрос B сработал, в середине можно увидеть столкновения. Кажется, что все загружается идеально, но когда вы загружаете представления, которые показывают контент HTML из локальной файловой системы, некоторые страницы содержат данные из ответов других запросов. Так что если ресурс А просто содержал фразу «Лиса красная и хитрая». и ресурс B содержит «Сова мрачна и мудра». Ответ на запрос A может в итоге оказаться неверным: «Лиса красная и хитрая. Сова», где ожидаемый результат был бы просто «Лиса красная и хитрая».
Я проследил все это до метода «didReceiveData: (NSData *) data», где полученные данные были повреждены, поэтому неудивительно, что они добавляются к общей переменной * responseData, которая добавляется к каждому при получении данных. и записывается в локальный файл только по методу didFinishLoading.
Я создал класс менеджера обновлений и пользовательский класс запросов, который я включу. Я надеюсь, что некоторые гуру, возможно, видели это раньше и могут помочь взломать этот.
// 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
/ * И МЕНЕДЖЕР ОБНОВЛЕНИЯ * /
// 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