如何在不阻塞主线程的情况下等待对 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文件
因此,通过所有这些示例,您可以管理接收数据的正确时机,并根据自己的意愿进行管理,并且您的主线程应用程序可以等待接收数据以供使用。
*/
当我的 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文件
因此,通过所有这些示例,您可以管理接收数据的正确时机,并根据自己的意愿进行管理,并且您的主线程应用程序可以等待接收数据以供使用。 */