当我在 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 发送表情符号字符时会收到陌生字符串

文本文件在ios目标c的邮件正文中显示为一个小框

当我发送超过过期日期的令牌时,为啥会收到 401 错误?

在 GIO 中,为啥这些异步文件 IO 操作永远不会完成? (适用于 C 和 Vala)

Laravel 异步请求的最佳实践