我正在将我的应用程序从Syncronous转换为Asyncronous HTTP请求,并遇到了一个问题,看起来它需要对应用程序处理数据的方式进行大量修改。让我试着解释
以前是这样的:
- Class1
,Class2
和Class3
都是UIViewController
的子类
-Helper类
- 内容显示类
他们做的事情大不相同,但共同的特点是他们与助手班级的互动。他们以多种不同的方式从用户收集请求的详细信息,然后最终向帮助者类发送请求 当它同步完成时,助手类将返回数据。然后,每个类将解释数据(XML文件)并通过segue
将它们传递给Content显示类大致如此:
的Class1:
//Get user input
SomeData *data = [helperclass makerequest];
id vcData = [data process];
[self performSegueWithIdentifier:@"segueIdentifier"];
---
- (void)prepareForSegue:(UIStoryboardSegue *)segue
{
DestinationViewController *destination = (DestinationViewController *)segue.destinationViewController;
destination.data = vcData;
}
内容显示类:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.data presentdata];
}
现在看起来像这样
我首先使用Class1来处理这个问题,以便将修复程序部署到class2和class3。所以class1和helper现在像这样进行交互
的Class1:
//Get user input
SomeData *data = [helperclass makerequestWithSender:self];
id vcData = [data process];
[self performSegueWithIdentifier:@"segueIdentifier"];
---
- (void)prepareForSegue:(UIStoryboardSegue *)segue
{
DestinationViewController *destination = (DestinationViewController *)segue.destinationViewController;
destination.data = vcData;
}
现在我面临的最大问题是如何将helperclass中的数据恢复为Class1
。我设法通过
(void)makeRequestWithSender:(Class1*)sender
{
[NSURLConnection sendAsynchronousRequest:...
{
[sender sendData:data];
}
}
但是,当我把它推到另外2个GUI类别时,它将构成我遇到困难的请求。我的第一个想法是设置sender:(id)
,但在[sender sendData:data]
行失败,告诉我id
没有方法sendData:
或类似方法。
希望我在这里不是太模糊,你们可以提供帮助。如果需要,我将能够发布代码片段,但是现在任何人都可以帮助更好地建议如何为此请求构建代码吗?
答案 0 :(得分:6)
您基本上想要使用'观察者模式'或(可能)略微更改的设置,因此您可以使用委托。
观察者模式
您可以通过NSNotificationCenter和NSNotifications获得机制。您的3个不同的UIViewController子类每个都订阅一个特定的NSNotification,您可以通过NSNotificationCenter发布通知来通知它们。
以下代码是您如何在viewcontroller子类中解决问题的示例:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// subscribe to a specific notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doSomethingWithTheData:) name:@"MyDataChangedNotification" object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// do not forget to unsubscribe the observer, or you may experience crashes towards a deallocated observer
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
...
- (void)doSomethingWithTheData:(NSNotification *)notification {
// you grab your data our of the notifications userinfo
MyDataObject *myChangedData = [[notification userInfo] objectForKey:@"myChangedDataKey"];
...
}
在你的助手类中,数据发生变化后你必须通知观察者,例如
-(void)myDataDidChangeHere {
MyDataObject *myChangedData = ...;
// you can add you data to the notification (to later access it in your viewcontrollers)
[[NSNotificationCenter defaultCenter] postNotificationName:@"MyDataChangedNotification" object:nil userInfo:@{@"myChangedDataKey" : myChangedData}];
}
通过@protocol
假设所有UIViewController子类都驻留在父视图控制器中,您可以在助手类中实现协议,并使父视图控制器成为委托。然后父视图控制器可以通过传递消息来通知子视图控制器。
您的帮助程序类声明可能如下所示(假设为ARC):
@protocol HelperDelegate;
@interface Helper : NSObject
@property (nonatomic, weak) id<HelperDelegate> delegate;
...
@end
@protocol HelperDelegate <NSObject>
-(void)helper:(Helper *)helper dataDidChange:(MyDataObject*)data;
@end
在帮助程序实现中,您将通过以下方式通知代理:
...
if ([self.delegate respondsToSelector:@selector(helper:dataDidChange:)]) {
[self.delegate helper:self dataDidChange:myChangedDataObject];
}
...
您的父视图控制器需要是辅助类的委托并实现其协议;声明中的粗略草图
@interface ParentViewController : UIViewController <HelperDelegate>
以及短版本的实施
// you alloc init your helper and assign the delegate to self, also of course implement the delegate method
-(void)helper:(Helper *)helper dataDidChange:(MyDataObject*)data {
[self.myCustomChildViewController doSomethingWithTheNewData:data];
}
<强>此外.. 强>
你可能会问自己喜欢哪种方法。两者都是可行的,主要的区别在于,通过观察者模式,您可以“立即”获得更多对象,而协议只能有一个代理,并且必须在必要时转发消息。关于利弊的讨论很多。一旦你下定决心,我建议你阅读它们(抱歉没有足够的声誉发布超过两个链接,所以请搜索stackoverflow)。如果不清楚,请询问。
答案 1 :(得分:1)
这里有一些合理的想法。详细说明/添加我的意见:
首先,哪个对象应该告诉下载程序(HelperClass
)开始下载?我的做法是在将呈现数据的视图控制器中执行此操作。所以我通常在segue之后(比如所提出的vc的viewWillAppear:
)开始网络请求,而不是之前。
接下来,当一个类需要执行为另一个类提供的代码时,我首先想一想使用block
来执行它是否有意义。通常(并非总是)块更有意义,并提供比委托,通知,KVO等更可读的代码。我认为NSURLConnection完成,例如,更适合块而不是委托。 (并且Apple已经同意,引入了+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
)。
所以我的应用模式就是这样:
// Class1.m
// when user has completed providing input
...
// don't do any request yet. just start a segue
[self performSegueWithIdentifier:@"ToContentDisplayClass" sender:self];
...
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// don't do a request yet, just marshall the data needed for the request
// and send it to the vc who actually cares about the request/result
if ([segue.identifier isEqualToString:@"ToContentDisplayClass"]) {
NSArray *userInput = // collect user input in a collection or custom object
ContentDisplayClass *vc = segue.destinationViewController;
vc.dataNeededForRequest = userInput;
}
...
然后在ContentDisplayClass.m
中// this is the class that will present the result, let it make the request
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
HelperClass *helper = [[HelperClass alloc]
initWithDataNeededForRequest:self.dataNeededForRequest];
// helper class forms a request using the data provided from the original vc,
// then...
[helper sendRequestWithCompletion:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
// interpret data, update view
self.label.text = // string we pulled out of data
} else {
// present an AlertView? dismiss this vc?
}
}];
这取决于HelperClass
实施block
形式的NSURLConnection
// HelperClass.m
- (id)initWithDataNeededForRequest:(id)dataNeededForRequest {
// standard init pattern, set properties from the param
}
- (void)sendRequestWithCompletion:(void (^)(NSURLResponse *, NSData *, NSError *))completion {
NSURLRequest *request = ...
// the stuff we need to formulate the request has been setup in init
// use NSURLConnection block method
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:completion];
}
编辑 - 在启动网络请求之前进行VC转换有几个基本原理:
1)围绕成功案例构建标准行为:除非应用程序是关于测试网络连接,否则成功的情况是请求有效。
2)应用程序的主要原则是响应,在用户操作后立即做一些明智的事情。因此,当用户做某事来启动请求时,立即进行vc转换是好的。 (而不是?旋转器?)。新呈现的UI甚至可以通过在运行时为用户提供新的内容来减少请求的感知延迟。
3)当请求失败时应用程序应该做什么?如果应用程序确实不需要请求有用,那么什么都不做是一个不错的选择,所以你想要成为新的vc。更典型地,该请求是必要的。用户界面也应该“响应”以请求失败。典型的行为是提供提供某种形式的“重试”或“取消”的警报。对于任何一种选择,UI想要的位置都在新的vc上。重试更为明显,因为它在尝试获取数据时总是如此。对于取消,“响应”取消的方式是回到旧的vc,vc转换回来并不难看,这是用户刚要求的。
答案 2 :(得分:0)
我不是100%清楚你现在如何处理数据,但是要将数据更改为异步调用,我会使用块。例如,您当前的同步代码如下:
//Get user input
data = [helperclass makerequest]
sendData = [data process]
会变成这样的东西:
//Get user input
data = [helperclass makerequestWithSuccess:^{
sendData = [data process]
}];
使用成功块将允许您等待处理数据,直到makerequest
完成。
您的新makerequest
功能现在看起来像这样:
-(void)makerequestWithSuccess:(void (^)(void))success{
// Put your makerequest code here
// After your makerequest is completed successfully, call:
success();
}
希望这有帮助!
答案 3 :(得分:0)
我不确定我过去所做的事情是否与您的问题相关,但我所做的是使用单一方法创建一个具有delegate
协议的下载类:{{ 1}}。
任何需要获取异步数据的类,都会创建此下载类的实例,并将自己设置为-(void)downloadFinished:(id) data
。我从delegate
和downloadFinished:
致电connection:didFailWithError:
。然后,在委托中实现该方法时,我会检查数据的类是connectionDidFinishLoading:
还是NSData
,然后评估该数据是否适用于该类。
答案 4 :(得分:0)
我不确定我是否正确理解了您的问题,但如果它有点:
代码示例如下所示:
typedef (void)(^completion_block_t)(id result);
-(void) asyncTaskA:(completion_block_t)completionHandler;
-(void) asyncTaskBWithInput:(id)input completion:(completion_block_t)completionHandler;
-(void) asyncTaskCWithInput:(id)input completion:(completion_block_t)completionHandler;
-(void) asyncSomethingWithCompletion:(completion_block_t)completionHandler;
-(void) asyncSomethingWithCompletion:(completion_block_t)completionHandler
{
[self asyncTaskA:^(id resultA){
if (![resultA isKindOfClass:[NSError class]]) {
[self asyncTaskBWithInput:resultA completion:^(id resultB){
if (![resultB isKindOfClass:[NSError class]]) {
[self asyncTaskCWithInput:resultB completion:^(id resultC) {
completionHandler(resultC);
}];
}
else {
completionHandler(resultB); // error;
}
}];
}
else {
completionHandler(resultA); // error
}
}];
}
你用它就像:
[self asyncSomethingWithCompletion:^(id result){
if ([result isKindOfClass:[NSError class]]) {
NSLog(@"ERROR: %@", error);
}
else {
// success!
self.myData = result;
}
}];
“延续”和错误处理使得这有点令人困惑(并且Objective-C语法并没有真正添加以提高可读性)。
可以写出相同的逻辑:
-(Promise*) asyncTaskA;
-(Promise*) asyncTaskBWithInput;
-(Promise*) asyncTaskCWithInput;
-(Promise*) asyncSomething;
- (Promise*) asyncSomething
{
return [self asyncTaskA]
.then(id^(id result) {
return [self asyncTaskBWithInput:result];
}, nil)
.then(id^(id result) {
return [self asyncTaskCWithInput:result];
}, nil);
}
使用如下:
[self asyncSomething]
.then(^(id result) {
self.myData = result;
return nil;
},
^id(NSError* error) {
NSLog(@"ERROR: %@", error);
return nil;
});
如果您更喜欢后者,可以在GitHub上使用“Promise”框架:RXPromise - 我是作者;)