NSURL Connection sendAsynchronousRequest 如何保存响应?

Posted

技术标签:

【中文标题】NSURL Connection sendAsynchronousRequest 如何保存响应?【英文标题】:NSURL Connection sendAsynchronousRequest How to save the response? 【发布时间】:2012-04-24 15:06:55 【问题描述】:
.h
@property (strong) NSString *reply;

我有以下方法:

.m
@synthesize reply;

- (void)send
 
 [NSURLConnection sendAsynchronousRequest:[self request] 
                 queue:[[NSOperationQueue alloc] init] 
                 completionHandler:
                        ^(NSURLResponse *response, NSData *data, NSError *error)       
                        
                            if (error)
                            
                                //NSLog(@"Error,%@", [error localizedDescription]);
                                [self setReply: [[NSString alloc] initWithString:[error localizedDescription]]];
                            
                            else 
                            
                                //NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
                                [self setReply: [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]];
                             
                        ];


现在,我尝试从块/处理程序返回一个值,但显然这是不可能的(或者我还没有弄清楚语法)。

我试图设置一个局部变量来获取回复(数据)的值,但它会产生错误。

不产生错误的唯一方法是使用类属性。但是当我尝试读取 [self reply] 的值时,它是 null,这让我觉得它永远不会被设置。

我知道sendAsync函数是线程异步的,所以[self reply]的值为null的原因,而当我使用sendSynchronousRequest时,我总是得到回复???

我做错了什么,如何从 completionHandler 中返回一个值???

Edit#1:在我看来,我做错了什么。我正在跟踪代码,当我使用同步发送时,一切正常。使用异步的,对 sendAsynchronousRequest 的实际调用从未执行,让我认为线程没有被调用,因此是空值。

Edit#2:我找到了解决这个问题的方法,添加以下内容:

[NSURLConnection sendAsynchronousRequest:[self request] 
                 queue:[NSOperationQueue alloc] init
                 completionHandler:
                        ^(NSURLResponse *response, NSData *data, NSError *error)       
                        
                            if (error)
                            
                                //NSLog(@"Error,%@", [error localizedDescription]);
                                [self setReply: [[NSString alloc] initWithString:[error localizedDescription]]];
                            
                            else 
                            
                                //NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
                                [self setReply: [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]];
                             
                        ];

[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 2]];

但是,我不完全确定我了解它的工作原理。我想 RunLoop 告诉异步操作运行 2 秒(我可以验证,因为将日期间隔设置为低值时,由于使用实时服务器进行测试,我得到一个空响应)。我不明白的是,当我只是告诉 RunLoop 运行时,它会永远阻塞一切,就好像当前线程从未终止一样。

【问题讨论】:

我不明白问题/问题。为什么不能从块完成函数返回值? setReply 函数有什么作用?向我们展示代码 设置断点以查看数据是否有效或为空。您走在正确的轨道上,这似乎是有效的,但您可能无法获得可以使用的信息。 块被指定为具有void返回类型,所以块本身不能返回值-但是为什么需要返回值呢?我猜你的 setReply 函数只是一个实例变量的设置器?在这种情况下,您不需要从块中返回任何内容,因为当您 setReply 时将捕获响应。使用局部变量捕获数据值时会产生什么错误?您应该能够设置断点来查看数据。另外,我会添加检查您收到的响应代码。 我基本上想要从 asyncRequest 中返回一个值(字符串)。我已经尝试搜索几个小时来了解如何从嵌套函数返回值,但无济于事。问题是“回复”从未设置,并且由于某种原因保持为空。 NSLog 确实打印出值/响应,但是当我尝试设置本地属性时,它没有设置。 【参考方案1】:

摆脱那个 runUntilDate 并尝试使用 [NSOperationQueue mainQueue],看看这是否会改变任何东西。

[NSURLConnection sendAsynchronousRequest:[self request] 
             queue:[NSOperationQueue mainQueue]
             completionHandler:
                    ^(NSURLResponse *response, NSData *data, NSError *error)...     

【讨论】:

我试过了,没有什么不同。这实际上是我第一次尝试的方式。【参考方案2】:

差不多一个月后,我找到了解决问题的方法:

    NSURLConnection* _connection = [[NSURLConnection alloc] initWithRequest:[self request] delegate:self startImmediately:NO];
    self.port = [NSPort port];                                                                                                       
    self.runloop = [NSRunLoop currentRunLoop];                                                                                       
    [self.runloop addPort:self.port forMode:NSDefaultRunLoopMode];
    [_connection scheduleInRunLoop:self.runloop forMode:NSDefaultRunLoopMode];
    [_connection start];
    while (self.finished != YES ) 
        [self.runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 0.1]];                                                                    
    
    [self.runloop removePort:[self port] forMode:NSDefaultRunLoopMode];
    [_connection unscheduleFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

这对我有用。请记住,我必须正确实现 NSConnection 的委托方法(didReceiveResponse、didReceiveData、didFailWithError、connectionDidFinishLoading)。 但是它现在异步工作,不会冻结 UI,并且有适当的错误报告。 希望它可以帮助遇到同样问题的人

【讨论】:

如果您投反对票 - 至少花点时间说出原因【参考方案3】:

您应该尝试使用 __block 关键字来声明您的属性回复

@property (strong) __block NSString *reply;

此关键字使您的属性可在块内写入。

编辑

关于阻塞主线程直到收到响应或直到请求超时的方法的建议:

//this block is used to define a waiting condition
typedef BOOL (^ CheckBlock)();

- (BOOL)waitUntilBlockTrue:(CheckBlock)theBlock orTimeout:(int)timeout 
    NSDate *waitingStartPoint = [NSDate date];
    BOOL stillNeedToWait = YES;
    while (stillNeedToWait && theBlock()) 
        stillNeedToWait = ([waitingStartPoint timeIntervalSinceNow]*(-1.0)) < timeout;
    
    return stillNeedToWait;

对于您的示例,您可以使用以下方法调用此方法:

[self waitUntilBlockTrue:^
        //returns YES as long as the method should block
        return (BOOL)(self.reply == nil);
     orTimeout:10];

【讨论】:

试过了,没用。我的猜测是主线程终止的速度比异步发送操作完成的速度快。谢谢你告诉我这个,很高兴知道。 如果你使用这个异步调用并且你希望主线程等待响应你必须阻塞主线程。我在使用 SenTestCase 编写测试时遇到了这个问题。查看我用来阻塞主线程的方法。 非常感谢您花时间回复并添加代码。不幸的是,我无法构建它,因为将错误类型的参数传递给块函数时出错(从 unsigned char BOOL 到 (BOOL)^() 的块指针转换无效)。此外,在我看来,我们正在以同样的方式使用不同的方法。我在主队列上添加了异步发送,然后告诉它 runLoop 10 秒(我的超时)。这不就是你正在做的事情吗,除了你所做的事情一旦返回一个值就会停止阻塞? 哦,我没有检查语法。向 BOOL 添加了强制转换,以便编译器知道块的返回类型。我不确定,但在您的情况下,runUntilDate 可能不会阻塞。您可以通过在 runUntilDate 调用之前和之后对接 NSLog 来检查这一点,并检查控制台中的消息是否真的是 2 秒间隔。 runUntilDate 的文档指出:“如果没有输入源或计时器附加到运行循环,则此方法立即退出”这可能是因为您专门创建了一个单独的 NSOperationQueue。 嗨!谢谢,它现在可以正确构建,但不知何故,我仍然没有得到回复的价值。我可能遗漏了一些东西或不了解 GCD 的工作原理。感谢您的帮助

以上是关于NSURL Connection sendAsynchronousRequest 如何保存响应?的主要内容,如果未能解决你的问题,请参考以下文章

如何创建类似 NSURLConnection 的东西?

空 JSON 响应

出现错误 - (BOOL)copyItemAtURL:(NSURL *)srcURL toURL:(NSURL *)dstURL 错误:(NSError **)error

NSURL

来自字符串的 NSURL 不起作用

IOS NSURL基本操作-备