今天早些时候,我提出以下问题: 当视图被推iOS版自停块
我提到的操作(OP1)实际上是一个“HTTP GET”到我的服务器,使用NSURLConnection的。
之后更是调查我发现,该块实际上并没有“死”。 真正的情况是,请求实际发送(服务器端会记录),即使在视图推(通过验证[NSThread睡眠:10])。 服务器响应,但这时如果视图2已被推什么也不会发生在应用程序方面! 仿佛连接已经失去了其委托! 另一种可能性IM看到的是“事实,NSURLConnection的是相关rsMainLoop?”
任何人都可以帮忙吗?
请不要忘记:
0一切只要视图2未被按压,直到操作完成正常工作。
1.请求被异步发送
2.我设置委托,并且只要认为不改变它的工作原理
3.视图1开始使用“单对象引用”属性“OP1Completed”的操作
4.视图2个检查OP1的经由propertie对“单对象引用”完成
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];
}
你可能会惊讶,但也有一对夫妇的解决方案-其中一些是很常见的,可以很容易地实现;)虽然,这个答案是荒谬的精心制作 ,给你的问题的实际解决方案将不会超过几行代码。 :)
你碰到了一个典型的“异步问题” - 嗯,这是不到一个问题,而是一个典型的编程任务今天。
你有什么是异步任务,OP1。 这将从视图控制器1(VC1)内启动,并在某个不确定的时间之后,它最终将产生两种结果或错误。
OP1的最终结果应该在以后VC2处理。
有几个方法,客户端可以如何获得最终结果 ,例如:通过国际志愿者组织,委托的方法,完成块,回调函数,未来或承诺,每个通知。
以上这些方法都有一个共同的特性:呼叫的网站获得通过异步结果提供者(而不是相反)通知。
轮询的结果,直到它可用,是个不错的办法。 Likewse,挂在信号灯和阻塞当前线程,直到结果为“信号”也同样不理想。
你可能熟悉结束块。 该通知调用点时,其结果是可以一个典型的异步方法是这样的:
typedef void (^completion_block_t)(id result);
- (void) doSomethingAsyncWithCompletion:(completion_block_t)completionHandler;
注: 通话现场向完成处理,而异步调用任务时,它完成的块,并且将其结果(或错误),以块的结果参数。 除非另有说明, 执行上下文 -这是线程或调度队列或NSOperationQueue -的将在其中执行的块是不知道。
但是考虑你的问题的时候,一个简单的异步功能和完成处理并不能产生一个可行的解决方案。 你不能传递“方法”很容易从VC1 VC2到再后来在VC2“附加”某种方式完成块。
幸运的是,任何异步任务可以被封装成NSOperation
。 一个NSOperation
具有完成块作为可以由调用点或其他地方设置一个属性。 和NSOperation
对象可以很容易地通过从VC1 VC2来。 VC2简单地增加了完成块操作,并最终当它的完成,其结果是可以得到通知。
然而,尽管这将是你的问题的可行解决方案 - 有其实用这种方法的几个问题 - 我不想阐述,而是提出了一个更好的一个:“承诺”。
A“无极”代表异步任务的最终结果 。 也就是说,承诺会存在即使异步任务的结果尚未进行评估。 一个承诺是一个普通的对象,您可以发送邮件。 因此,承诺可以通过周围很像NSOperations。 一个承诺是一个异步方法/函数的返回值 :
-(Promise*) doSomethingAsync;
不要失配的承诺与异步函数/方法/任务/操作-许是任务的最终结果只是一种表象。
承诺必须由异步任务被最终解决 -也就是说,该任务必须发出一个承诺“兑现”的消息,结果值,否则它必须用一个错误一起发送的诺言“拒绝”的消息。 该承诺保持从任务通过了结果值的参考。
承诺可以一次解决!
为了获得最终结果的客户端可以“注册” 成功的处理程序和错误处理程序 。 当任务履行的承诺(即,它是成功的)成功处理程序将被调用,当任务否决了的原因是一个错误的对象传递承诺的错误处理程序将被调用。
假设一个特定实现承诺,解决一个承诺可能是这样的:
- (Promise*) task {
Promise* promise = [Promise new];
dispatch_async(private_queue, ^{
...
if (success) {
[promise fulfillWithValue:result];
}
else {
NSError* error = ...;
[promise rejectWithReason:error];
}
});
return promise;
}
客户端“注册”的处理程序获得的最终结果如下:
Promise* promise = [self fetchUsers];
promise.then( <success handler block>, <error handler block> );
成功处理程序和错误处理程序块声明如下:
typedef id (^success_handler_block)(id result);
typedef id (^error_handler_block)(NSError* error);
为了只是“注册” 成功处理程序 (的情况下,异步任务“返回”成功)要这样写:
promise.then(^id(id users) {
NSLog(@"Users:", users);
return nil;
}, nil);
如果任务成功完成,该处理器将被调用 - 其用户打印到控制台。 如果任务失败,成功处理程序将不会被调用。
为了只是“登记”的错误处理程序 (的情况下,异步任务失败)要这样写:
promise.then(nil, ^id(NSError* error) {
NSLog(@"ERROR:", error);
return nil;
}, nil);
如果任务成功完成,错误处理程序将不会被调用。 只有当任务失败(或子任务),这个错误处理程序将被调用。
当异步任务的结果是最终可用,处理程序中的代码会被执行“在一些未指定执行上下文”。 这意味着,它可以在任何线程中执行。 (注:有办法来指定执行上下文,说主线程)。
允诺可以注册多个处理程序对。 只要你想,你可以添加尽可能多的处理程序,以及何时 何地你想要的。 现在,你应该明白您的实际问题的连接:
你可以开始在VC1异步任务,并得到一个承诺。 然后通过这个承诺VC2。 在VC2您可以将您的处理程序,当结果最终可用,这将被调用。
当结果合格时承诺VC2,也就是说,当承诺已被解决实际已经可以不用担心。 您仍然可以添加处理程序,他们得到适当的(立即)射击。
您也可以在“链”多任务 - 那就是,当任务1完成后调用TASK2一次。 由四个异步任务的“产业链”或“继续”看起来如下:
Promise* task4Promise =
[self task1]
.then(^id(id result1){
return [task2WithInput:result1];
}, nil)
.then(^id(id result2){
return [task3WithInput:result2];
}, nil)
.then(^id(id result3){
return [task4WithInput:result3];
}, nil);
task4Promise代表的最终结果task4WithInput:
人们也可以并行执行任务,如taskB和taskC将在并行任务A时,已经成功完成上手:
Promise* root = [self taskA];
root.then(^id(id result){
return [self taskB];
}, nil);
root.then(^id(id result){
return [self taskC];
}, nil);
使用这种方案,可以定义任务的一个非循环图,其中,每个依赖于它的后继者(“父”)的成功执行。 “错误”将通过传递给根,并且由最后一个错误处理程序(如果有的话)来处理。
有对Objective-C的几个实现。 我已经写了一个自己:“RXPromise”(可在GitHub上)。 其中最强的特征是“取消” - 这是不是承诺的标准功能,但在RXPromise实现。 有了这个,你可以选择取消的异步任务的树。
还有很多更多的承诺。 你可以在网上搜索,尤其是在JavaScript社区。
我不知道我的理解是,在第一控制器继续工作流程 - 具体地说,用户确实开始下载,和他做了一个控制器被提出之前,还有什么什么(当控制器被实例化)。 当我做了该做需要从多个类下载过去的应用程序,我创建了创建NSURLConnection的,并实现了所有的回调下载类。 它有一个代表协议方法将数据(或者原始数据或错误对象)发送回其委托。
我做了一个简单的测试案例模拟什么,我认为您的工作流程是,用两个按钮。 一个实例化一个下载器类的实例,创建一个控制器,将其设置为下载的代表,并开始下载。 第二按钮确实推到第二控制器。 这工作,无论何时推情况,但我不知道这是否是有关你的情况(我测试使用网络链接润发模拟慢速连接)。
第一控制器:
#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;
下载.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);
}
所以,这里是什么,我认为将是肯定的工作:
传旗新控制器。 如果标记为未完成,然后开始过载在新的VC,确保没有,直到它完成加载的数据显示出来。
我不认为这是奇怪的是,线程停止不过,随着新的VC推,因为当我与派遣AFNetworking异步调用,它继续新的VC推后还要加载。 或许,如果你使用的是不同的框架,你应该使用AFNetworking。
因此,如果新的VC是在推后,你的线程实际上并继续(我怀疑它-你只是觉得它并没有继续下去,因为它崩溃的代码),那么请尝试以下操作:
一)通过标志,如果操作完成后,继续进行通常
b)如不是,不加载任何和调用某种来检查,如果该标志被设置,并且如果是返回数据两者之间委托方法。
如果您有关于如何建立一个委托的问题,只是问,我可以在一些细节填写。
作为已经在评论中你提到的第一个问题:你有可能有两个问题:
- 设计问题
- A码的问题,导致该块。 (但没有代码,这是很难搞清楚)。
让我们提出一个切实可行的方法:
再说了,我们单是一些“装载机”类执行HTTP请求。 相反,它决定了网络请求的状态属性的查询,您应该返回一些对象 ,你可以要求国家,甚至更好,其中VC2可以注册 完成块 时请求完成它被调用。
一个NSOperation
可以“用”来表示异步网络请求的最终结果。 但是,这是一个有点笨拙 - 假设我们有一个子类RequestOperation:
RequestOperation* requestOp = [[Loader sharedLoader] fetchWithURL:url];
现在,“requestOp”代表你的网络请求,包括最终的结果。
您可以在VC1获得此操作。
你可能不想问共享的加载器有关的特定操作,因为它可能无国籍的 -也就是说,它本身并不跟踪的请求操作。 想想,你要使用的类Loader
多次启动网络请求-并行成为可能。 然后,你指的是哪个请求时,你问的一个属性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 -即开始执行handlerOp时requestOp完成:
[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;
}
我已经想通了。 在我的第二个观点(其中i为W8操作完成),我不能W8采用ThreadSleep! 我必须使用[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];