当我在 iOS 的目标 c 中异步发送请求时,为啥我的数据会损坏?
Posted
技术标签:
【中文标题】当我在 iOS 的目标 c 中异步发送请求时,为啥我的数据会损坏?【英文标题】:Why is my data getting corrupted when I send requests asynchronously in objective c for iOS?当我在 iOS 的目标 c 中异步发送请求时,为什么我的数据会损坏? 【发布时间】:2012-02-27 01:37:48 【问题描述】:我在使用基本内容交付应用时遇到了问题。这个概念很简单,因为应用程序应该定期加载更新。第一次,本地设备上没有数据可以通过,所以我在更新过程中拉下了许多文件(全部)。更新进度通过 UIProgressView 将预期字节与接收字节进行比较报告给用户。问题是大约 90% 的时间一切都进行得很顺利,但有时也会发生碰撞。冲突的证据是由以下一系列可追踪事件产生的:1)开始请求 A。2)接收到资源 A 的数据。3)接收到(更多)资源 A 的数据。4)完成了对 A 的请求。 5) 对资源 B 的请求 B 遵循相同的流程。
**请注意,这种情况发生在大约 1200 个小型 html 页面的请求中。
即使我记录了每个事件并确保在请求 B 被触发之前已完全接收到资源 A,但在中间,仍然可以看到冲突。一切似乎都可以完美加载,但是当您从本地文件系统加载显示 HTML 内容的视图时,某些页面会包含来自其他请求响应的数据。因此,如果资源 A 只包含短语“狐狸又红又狡猾”。资源 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
【问题讨论】:
【参考方案1】:根据苹果文档(我的很多答案似乎都是这样开始的):
- (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];
【讨论】:
感谢您的洞察力。不幸的是,在我将接收到的数据写入累积 responseData 实例变量之前,它显示为已损坏。在 didReceiveData:(NSData *)data 方法期间,我打印出该方法的数据局部变量的结果。它已经有额外的数据。另一个重要方面是我知道没有重定向,因为我编写了服务器端并在客户端调用它们时将响应记录在 apache 日志中。它们在 apache 的日志中是正确的。在传递结果时它们已经以某种方式被转换了 didReceiveData 对于 didReceiveResponse 方法,它的 NSURLResponse 是否一定包含最终的响应负载。是我完全绕过 didReceiveData 的建议吗?感谢您的信息。以上是关于当我在 iOS 的目标 c 中异步发送请求时,为啥我的数据会损坏?的主要内容,如果未能解决你的问题,请参考以下文章
iOS(目标c)将带有json的post请求发送到php后端
为啥当我使用 nativescript 从 iOS 发送表情符号字符时会收到陌生字符串