NSURLConnection不会跨多个视图调用完成

时间:2013-09-05 23:13:11

标签: ios objective-c delegates nsurlconnection

今天早些时候我提出了以下问题:iOS block being stoped when view pushed

我提到的操作(OP1)实际上是我的服务器的“http get”,使用NSURLConnection。
经过更多调查后,我发现该块实际上并没有“死”。真正发生的是请求实际上是SENT(服务器端记录它),即使在推送视图之后(通过[NSThread sleep:10]验证)。服务器响应,但如果已经推送了view2,那么应用程序端就会发生NOTHING!几乎就好像连接丢失了它的代表!我看到的另一种可能性是“NSURLConnection与 rsMainLoop 有关吗?”

有人可以帮忙吗?

请不要忘记:
0.只要在操作完成之前未按下view2,一切正常 1.请求是异步发送的 2.我设置了委托,只要视图不变,它就可以工作 3. view 1 使用“singleton object reference”属性“OP1Completed”开始操作
4.视图 2 通过“单件对象参考”上的属性检查OP1的完成情况 5.查看 2 通过转到“singleton.OP1Result”属性获取“结果”

编辑1:
好吧,让我们有一些代码。首先是我的单身人士的相关代码(名为“交互”):

-(void)loadAllContextsForUser:(NSString *)username{
userNameAux = username;
_loadingContextsCompleted = NO;
if (contextsLoaderQueue == NULL) {
    contextsLoaderQueue = dispatch_queue_create("contextsLoaderQueue", NULL);
}

dispatch_async(contextsLoaderQueue, ^{
    NSLog(@"Loading all contexts block started");
    [self requestConnectivity];

    dispatch_async(dispatch_get_main_queue(), ^{
        [Util Get:[NSString stringWithFormat:@"%@/userContext?username=%@", Util.azureBaseUrl, [username stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]
     successBlock:^(NSData *data, id jsonData){
         NSLog(@"Loading all contexts block succeeded");
         if([userNameAux isEqualToString:username]){
             _allContextsForCurrentUser = [[NSSet alloc]initWithArray: jsonData];
         }
     } errorBlock:^(NSError *error){
         NSLog(@"%@",error);
     } completeBlock:^{
         NSLog(@"load all contexts for user async block completed.");
         _loadingContextsCompleted = YES;
         [self releaseConnectivity];
     }];
    });

    while (!_loadingContextsCompleted) {
        NSLog(@"loading all contexts block waiting.");
        [NSThread sleepForTimeInterval:.5];
    }
});
NSLog(@"Load All Contexts Dispatched. It should start at any moment if it not already.");
}

这是类Util,它实际上处理请求/响应

-(id)initGet:(NSString *)resourceURL successBlock:(successBlock_t)successBlock errorBlock:(errorBlock_t)errorBlock completeBlock:(completeBlock_t)completeBlock;{
if(self=[super init]){
    _data=[[NSMutableData alloc]init];
}

_successBlock = [successBlock copy];
_completeBlock = [completeBlock copy];
_errorBlock = [errorBlock copy];

NSURL *url = [NSURL URLWithString:resourceURL];
NSMutableURLRequest *request = [NSURLRequest requestWithURL:url];
[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
//[_conn scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
//[_conn start];
NSLog(@"Request Started.");

return self;
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    [_data setLength:0];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
id jsonObjects = [NSJSONSerialization JSONObjectWithData:_data options:NSJSONReadingMutableContainers error:nil];

id key = [[jsonObjects allKeys] objectAtIndex:0];
id jsonResult = [jsonObjects objectForKey:key];

_successBlock(_data, jsonResult);
_completeBlock();
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
_errorBlock(error);
_completeBlock();
}

最后这里是相关部分VC1(推入VC2)

- (IBAction)loginClicked {
NSLog(@"login clicked. Preparing to exibit next view");

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard_iPhone" bundle:nil];
AuthenticationViewController *viewController = (AuthenticationViewController *)[storyboard instantiateViewControllerWithIdentifier:@"ContextSelectionView"];

NSLog(@"Preparation completed. pushing view now");

[self presentViewController:viewController animated:YES completion:nil];
}

4 个答案:

答案 0 :(得分:1)

我不确定我是否了解第一个控制器中正在进行的工作流程 - 具体来说,用户启动下载的操作是什么,以及在下一个控制器出现之前他做了什么(当控制器获得时实例化)。当我过去制作需要从多个类下载的应用程序时,我创建了一个创建NSURLConnection的下载类,并实现了所有的回调。它有一个委托协议方法将数据(原始数据或错误对象)发送回其委托。

我做了一个简单的测试用例,使用两个按钮来模拟我认为你的工作流程。一个实例化Downloader类实例,创建下一个控制器,将其设置为下载器的委托,并开始下载。第二个按钮按下第二个控制器。无论何时发生推送,这都有效,但我不知道它是否与您的情况相关(我使用网络链路调节器测试来模拟慢速连接)。

第一个控制器:

#import "ViewController.h"
#import "ReceivingViewController.h"
#import "Downloader.h"

@interface ViewController ()
@property (strong,nonatomic) ReceivingViewController *receiver;
@end

@implementation ViewController

-(IBAction)buttonClicked:(id)sender {
    Downloader *loader = [Downloader new];
    self.receiver = [self.storyboard instantiateViewControllerWithIdentifier:@"Receiver"];
    loader.delegate = self.receiver;
    [loader startLoad];
}

-(IBAction)goToReceiver:(id)sender {
    [self.navigationController pushViewController:self.receiver animated:YES];
}

下载课程.h:

@protocol DownloadCompleted <NSObject>
-(void)downloadedFinished:(id) dataOrError;
@end

@interface Downloader : NSObject

@property (strong,nonatomic) NSMutableData *receivedData;
@property (weak,nonatomic) id <DownloadCompleted> delegate;

-(void)startLoad;

Downloader .m:

-(void)startLoad {
    NSLog(@"start");
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.google.com"] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
    NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
    if (connection) self.receivedData = [NSMutableData new];
}

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    self.receivedData.length = 0;
}


-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.receivedData appendData:data];
}


-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [self.delegate downloadedFinished:error];
}


-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [self.delegate downloadedFinished:self.receivedData];
}

-(void)dealloc {
    NSLog(@"In Downloader dealloc. loader is: %@",self);
}

第二个控制器:

@interface ReceivingViewController ()
@property (strong,nonatomic) NSData *theData;
@end

@implementation ReceivingViewController


-(void)downloadedFinished:(id)dataOrError {
    self.theData = (NSData *)dataOrError;
    NSLog(@"%@",self.theData);
}

-(void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    NSLog(@"%@",self.theData);
}

答案 1 :(得分:0)

所以,我认为这是肯定的:

将标志传递给新控制器。如果标志未完成,则在新VC中重新开始加载,并确保在加载完成之前不显示任何数据。

我认为线程停止虽然推送新VC是很奇怪的,因为当我使用AFNetworking调度异步调用时,即使在推送新的VC之后它也会继续加载。也许如果您使用的是其他框架,则应使用AFNetworking。

所以,如果您的线程实际上 继续推送新VC后继续(因为我怀疑它确实存在 - 您只是认为它不会继续,因为它会崩溃代码),然后尝试以下内容:

a)传递标志,如果操作完成,则正常进行 b)如果没有,不加载任何东西并在两者之间调用某种委托方法来检查是否设置了标志,如果是,则返回数据。

如果您对如何设置代表有疑问,请询问,我可以填写一些详细信息。

答案 2 :(得分:0)

正如你在第一个问题的评论中已经提到的那样:你可能有两个问题:

  1. 设计问题
  2. 导致阻止的代码问题。 (但没有代码,这很难弄明白。)
  3. 让我们提出一个实用的方法:

    说,我们的单例是一些执行HTTP请求的“Loader”类。您应该返回某些可以请求状态的对象,而不是轮询确定网络请求状态的属性,或者更好的地方,VC2可以注册一个完成块,当请求完成时,它将被称为

    可以“使用”NSOperation来表示异步网络请求的最终结果。但这有点笨拙 - 假设我们有一个子类RequestOperation:

    RequestOperation* requestOp = [[Loader sharedLoader] fetchWithURL:url];
    

    现在,“requestOp”代表您的网络请求,包括最终结果。

    您可以在VC1中获取此操作。

    您可能不想向共享加载程序询问特定操作,因为它可能无状态 - 也就是说,它本身不跟踪请求操作。考虑一下,您希望多次使用类Loader来启动网络请求 - 可以并行执行。那么,哪个请求你的意思是当你问{em>一个属性Loader时,它会告诉你一些关于请求状态的信息? (它不起作用)。

    所以,再次回到工作方法和VC1:

    假设在VC1中你获得了RequestOperation对象,它是NSOperation的子类。假设RequestOperation有一个属性responseBody - 这是一个NSData对象,表示请求操作的最终响应数据。

    为了获取请求的最终响应主体,您不能只询问属性:连接可能仍在运行 - 您将获得nil或垃圾,或者您可能会阻止线程。行为取决于RequestOperation的实现。

    解决方案如下:

    在VC2中:

    我们假设,VC1已将 requestOp “传递”到VC2(例如在prepareForSegue:sender:中)。

    为了以异步正确的方式检索响应主体,您需要一些额外的步骤:

    创建一个NSBlockOperation,它执行一个处理响应主体的块,例如:

    NSBlockOperation* handlerOp = [NSBlockOperation blockOperationWithBlock:^{
        NSData* body = requestOp.responseBody;
        dispatch_async(dispatch_get_main_queue(), ^{
            self.model = body;
            [self.tableView reloadData];
        });
    }];
    

    然后,使 handlerOp 依赖于 requestOp - 也就是说,当 requestOp 完成时,开始执行 handlerOp

    [handlerOP addDependency:requestOp];
    

    handlerOp 添加到队列中,以便执行:

    [[NSOperation mainQueue] addOperation:handlerOp];
    

    这仍然要求你“异步”思考 - 没有办法解决这个问题。最好的是,要习惯实用的模式和习语。


    另一种方法是使用RXPromise(来自第三方库):

    在VC1中:

    requestPromise = [Loader fetchWithURL:url];
    

    现在,在VC2中:

    我们假设,VC1已将 requestPromise “传递”到VC2(例如在prepareForSegue:sender:中)。

    例如viewDidLoad

    requestPromise.thenOn(dispatch_get_main_queue(), ^id(id responseBody){
        // executes on main thread!
        self.model = responseBody;
        [self.tableView reloadData];
        return nil;
    }, nil);
    

    加成:

    如果需要,您可以随时通过发送cancel取消网络请求

    - (void)viewWillDisappear:(BOOL)animated {
        [super viewWillDisappear:animated];
        [self.requestPromise cancel];
        self.requestPromise = nil;
    }
    

答案 3 :(得分:0)

&#39; ave想出来了。在我的第二个视图中(我完成操作的w8)我不能使用Thread Sleep!我必须使用[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];