如何在不阻塞主线程的情况下等待Web调用在iOS上返回数据?

时间:2016-02-01 19:03:21

标签: ios nsurlconnection nsurlrequest

当我的iOS应用程序启动时,我需要从我的服务器获取一些关键设置(例如:{{3}})。我需要在之前获取此数据我加载用户的数据。

进行此次调用的正确方法是什么,基本上“暂停”应用程序,但仍然没有阻止主线程并且遵守规定?

目前我正在做这个例子,这显然是不正确的,因为它完全阻止了一切:

NSData *myRequestData = [NSData dataWithBytes:[myRequestString UTF8String] length:[myRequestString length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: [NSURL URLWithString: myURLString]]; 
[request setHTTPMethod: @"POST"];
[request setHTTPBody:myRequestData];
NSURLResponse *response;
NSError *error;
NSString *returnString = [[NSString alloc] init];

returnString = [[NSString alloc] initWithData:[NSURLConnection sendSynchronousRequest:request 
                                                                               returningResponse:&response 
                                                                                              error:&error] 
                                                   encoding: NSASCIIStringEncoding];

3 个答案:

答案 0 :(得分:3)

您可以使用NSURLConnection sendSynchronousRequest而不是NSURLSessionDataTask。此外,从iOS 9中不推荐sendSynchronousRequest。代码如下

NSData *myRequestData = [NSData dataWithBytes:[myRequestString UTF8String] length:[myRequestString length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: [NSURL URLWithString: myURLString]]; 
[request setHTTPMethod: @"POST"];
[request setHTTPBody:myRequestData];

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
                                  {
                                      NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                                      if ([httpResponse statusCode]==200) {
                                          dispatch_async(dispatch_get_main_queue(), ^{
                                              NSString* returnString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                                              //do whatever operations necessary on main thread
                                          });
                                      }
                                      else
                                      {
                                          dispatch_async(dispatch_get_main_queue(), ^{
                                              //action on failure
                                          });
                                      }
                                  }];
[dataTask resume];

由于它是异步操作,因此不会阻止主线程。就暂停应用而言,您可以在拨打电话之前显示某种活动指示或禁用用户互动。一旦响应出现隐藏指示或启用UI。

答案 1 :(得分:3)

你应该 使用sendSynchronousRequest。你应该假装该方法不存在。在主线程上调用它是一个非常糟糕的主意,如果远程服务器需要几秒钟的时间来处理您的请求,系统可能会终止您的应用程序。

正如Vishnu建议的那样,请改用NSURLSession

如果你想"暂停应用"当您的内容加载时,在加载发生时显示某种模态警报/视图。一种选择是使用UIAlertController而不添加任何操作,然后在加载完成后调用dismissViewController:animated:

我做的另一件事是显示一个完全覆盖屏幕的视图,填充50%不透明的黑色,并在其中放入一个带有进度指示器的视图,加上请等待消息(以及任何你想要的其他化妆品。)50%不透明的视图都会使屏幕的其余部分变暗,并阻止用户点击任何内容,进度指示器让用户知道你的应用程序正在运行。

这两种方法都有效,因为您的下载是异步进行的,因此您可以显示警报或进度指示器,并按预期进行动画制作。

答案 2 :(得分:1)

/ **

您可以使用协议来处理该问题,例如,您可以创建一个连接到您可以使用NSURLSession的URL的类,但您似乎熟悉NSURLConnection。因此,让我们创建一个类,它将连接并从您的URL接收信息,如下所示:

H档

* /

#import <Foundation/Foundation.h>

/* Here is the custome protocol to manage the response at will */
@protocol OnResponseDelegate;

/* Here the class is implementing the protocol to receive the callbaks from the NSURLConnection when the request is processing */
@interface WgetData : NSObject <NSURLConnectionDataDelegate>

@property (strong, nonatomic) id<OnResponseDelegate>handler;
@property (strong, nonatomic) NSMutableData *responseData;
@property (strong, nonatomic) NSURLConnection *connection;
@property (strong, nonatomic) NSMutableURLRequest *request;

-(id)initWithUrl:(NSString*)url postBody:(NSString*)body delegate:(id)delegate;

-(void)execute;

@end

/* here is the real definition of the protocol to manage the response */
@protocol OnResponseDelegate <NSObject>

-(void)onPreStart;
-(void)onResponse:(NSData*)response;

@end

/ * 结束H档

上面的代码使用协议来处理您希望从URL接收的数据。让我们执行它看起来像这样的实现文件:

M档

* /

#import "WgetData.h"
#define TIMEOUT 700

static NSString * const CONTENT_TYPE_VALUE = @"application/json";
static NSString * const CONTENT_TYPE_HEADER = @"Content-Type";
static NSString * const GET = @"GET";
static NSString * const POST = @"POST";

@implementation WgetData

@synthesize handler,responseData,connection,request;

-(id)initWithUrl:(NSString *)url postBody:(NSString *)body delegate:(id)delegate{

    NSURL *requestUrl = [NSURL URLWithString:[url stringByAddingPercentEncodingWithAllowedCharacters: NSCharacterSet.URLQueryAllowedCharacterSet]];

    [self setRequest:[NSMutableURLRequest requestWithURL:requestUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:TIMEOUT]];
    [self setResponseData:[NSMutableData new]];
    [self setHandler:delegate];

    [request setHTTPMethod:POST];
    [request addValue:CONTENT_TYPE_VALUE forHTTPHeaderField:CONTENT_TYPE_HEADER];
    [request setHTTPBody: [body dataUsingEncoding:NSUTF8StringEncoding]];

    return self;
}

-(void)execute{
    //here is the moment to prepare something in your main class before send the request to your URL
    [handler onPreStart];
    [self setConnection:[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]];
}

/* this method belongs to the implementation of the NSURLConnectionDataDelegate to receive the data little by little */
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    [self.responseData appendData:data];
}

/* this method belongs to the implementation of the NSURLConnectyionDataDelegate that is runned only when the data received is complete */
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{

    /* if this method happen it means that the data is ready to delivery */

    /* sending the information to the class that implements this class through the handler or delegate */
    [handler onResponse:responseData];
}

@end

/ *

结束M档

然后你只需要用下一段代码实现这个类 例如,在viewDidLoad

中的ViewController中

记得将协议称为委托,以正确的方式实现方法

H文件ViewController示例

* /

#import <UIKit/UIKit.h>
#import "WgetData.h"

@interface ViewController : UIViewController <OnResponseDelegate>

@end

/ *

结束H ViewController文件

M ViewController文件

* /

@interface MainViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    WgetData *getData = [[WgetData alloc] initWithUrl:@"http://myurl.net" delegate:self];
    [getData execute];

}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void) onPreStart{
    // prepare something before run the request to your URL
}

- (void) onResponse:(NSData *)response{
    // receive the data when the data is ready and convert o parse to String or JSON or Bytes or whatever
}

@end

/ *结束M档

因此,在所有这个示例中,您可以管理正确的时刻,当您收到要按照自己的意愿管理的数据时,您的主线程应用程序可以等待您的数据将被接收以备使用。 * /