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

How do I wait for a web call to return data on iOS without blocking the main thread?

当我的 iOS 应用程序启动时,我需要从我的服务器获取一些关键设置(例如:http://www.example.com/critical_app_settings.php)。我需要在加载用户数据之前获取这些数据。

进行此调用的正确方法是什么,基本上是 "pause" 应用程序,但仍不阻塞主线程并保持良好的合规性?

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

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];

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

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];

因为是异步操作,你的主线程不会被阻塞。就暂停应用程序而言,您可以显示某种 activity 指示符或在拨打电话之前禁用用户交互。一旦响应出现隐藏指示器或启用 UI.

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

如 Vishnu 所建议,请改用 NSURLSession

如果您想 "pause the app" 在您的内容加载时显示某种模态 alert/view 在加载发生时。一种选择是使用不添加任何操作的 UIAlertController,然后在加载完成后调用 dismissViewController:animated:

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

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

/**

你可以使用一个协议来为你处理,例如你可以创建一个连接到 URL 的 class 你可以使用 NSURLSession 但看起来您熟悉 NSURLConnection。因此,让我们创建您的 class,它将连接并从您的 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文件

那你只需要用下一段代码实现这个class 例如在 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文件

因此,通过所有这些示例,您可以管理接收数据的正确时机,并根据自己的意愿进行管理,并且您的主线程应用程序可以等待接收数据以供使用。 */