6

通常,在 GUI 中,我面临的程序流程本质上是顺序的,但涉及异步操作(例如NSURLConnection下载内容)和 UI 操作(等待用户在 中选择一个选项UIActionSheet)。

只是为了说明,例如,

  1. 显示一个UIActionSheet,提示用户选择一种颜色。
  2. 如果颜色为蓝色,请从服务器下载文件。
    1. 如果下载失败,通知用户(UIAlertView,等待点击确定)
    2. 如果用户在 中选择“是” UIAlertView,请重试下载。
  3. 如果颜色是黑色,请执行其他操作。
  4. 等等等等。

流程是顺序的——我们不会在 1 之前做 2。但是由于异步操作和 UI 元素,如果我们使用委托(或块),这很快就会变成意大利面条代码。

是否有编写此类代码的通用方法?

4

1 回答 1

9

有一个名为Reactive Cocoa的库非常棒,但很难习惯。

实现您的目标的一种更简单的方法是使用围绕 UIAlertView 和 UIActionSheet的块包装器来实现您的目标。这也假设您的网络代码中有回调块。

例子:

- (void)showActionSheet
{
    BlockActionSheet *sheet = [BlockActionSheet sheetWithTitle:@"Choose one"];
    __weak BlockActionSheet *weakSheet = sheet;

    [sheet addButtonWithTitle:@"Blue" atIndex:0 block:^{
        [self downloadFileFromServerSuccessBlock:^{
            //YAY
        } failureBlock:^{
            BlockAlertView *alert = [BlockAlertView alertWithTitle:@"Failure" message:@"Something Went Wrong"];
            [alert addButtonWithTitle:@"Try Again" block:^{
                [weakSheet showInView:self.view];
            }];
            [alert setCancelButtonWithTitle:@"Cancel" block:nil];
        }];
    }];

    [sheet addButtonWithTitle:@"Black" atIndex:1 block:^{
        //something else
    }];

    [sheet setCancelButtonWithTitle:@"Cancel" block:nil];

    [sheet showInView:self.view];
}

所以最后一行“[sheet showInView:self.view]”开始了整个事情。如果他们选择蓝色,则调用该块。您的网络代码也支持块,因此您可以从那里获得成功和失败块回调。如果失败,那么您会弹出一个基于块的警报视图,使 ActionSheet 重新显示自己。

我希望这至少有一点帮助。此外,“self.view”调用可能会发生一些强引用,所以我也会对视图进行弱引用。

这是 ReactiveCocoa 示例。我对这个框架很陌生,所以我希望我能正确使用它。

/*
 *  This function is getting called from a login view controller.
 */

- (void)sendAuthentication
{
    /*
     *  A RACSubscribable will invoke the where^ block when it calls [sendNext:];
     *  That call is coming from the ThaweQBRequestController.
     *  If the where^ block return YES then the subscribeNext^ block will get called.
     *  asMaybes wraps the object I send back in a RACMaybe object which has a convenience method [hasObject];
     *  The subscribeNext^ block has an object coming into it, that object is an array in this case that I sent
     *  from the ThaweQBRequestController.
     */

    RACSubscribable *sub = [[ThaweQBRequestController logInWithUsername:self.thaweusername password:self.thawepassword] asMaybes];

    [[sub where:^(id x) {
        return [x hasObject];
    }]
     subscribeNext:^(id _) {

         NSArray *array = [_ object];

         NSString *errcode = [array objectAtIndex:0];

         if (errcode.boolValue == YES)
             //YAY SUCCESS!!!

         } else {
             //UH OH
         }
         // Update UI, the array has the username and password in it if I want to display or whatever is clever.
     }];
}

然后我们进入我拥有的网络请求控制器。

+ (RACAsyncSubject *)logInWithUsername:(NSString *)username password:(NSString *)password
{
    /*
     *  First we have our loginCommand. It will invoke the block we give it on a background thread.
     *  This block returns a Subscribable. We can subscribe other blocks to be invoked when the aync call is done.
     *  In this case we are creating loginResult to retrieve the async call.
     */

    RACAsyncCommand *loginCommand = [RACAsyncCommand command];

    /*
     *  Now we have our AsyncSubject. We are instantiated it here and are going to pass it back to the login view controller;
     *  When our async call finishes and we filter through the results we will call [subject sendNext:] to invoke blocks we created in
     *  the login view controller;
     *  We will filter the results uses the loginResult blocks.
     */
    RACAsyncSubject *subject = [RACAsyncSubject subject];

    /*
     *  loginResult is a "Subscribable". Every time it gets a [sendNext:] call it runs the blocks assosiated with it.
     *  These [sendNext:] calls are coming from our network code.
     *  'repeat' means that even after the async block is invoked it keeps a reference to it incase we want to use it again.
     *  'asMaybes' wraps a RACMaybe object around the object you send to the loginResult blocks. The benefit of a RACMaybe is
     *  that it has some convienence methods like 'hasError' and 'hasObject'.
     */

    RACSubscribable *loginResult = [[[loginCommand
                                      addAsyncBlock:^(id _) {
                                          return [self authenticateUser:username password:password];
                                      }]
                                     repeat]
                                    asMaybes];

    /*
     *  The first block, where^, get called every time loginResult calls [sendNext:].
     *  If it returns YES then the select^ block gets called. The select^ block invokes the subscribeNext^ block and sends it the
     *  error.
     */

    [[[loginResult
       where:^(id x) {
           return [x hasError];
       }]
      select:^(id x) {
          return [x error];
      }]
     subscribeNext:^(id x) {
         NSLog(@"network error omg: %@", x);
     }];

    /*
     *  Same as above except this time we are looking for instances when we have an object instead of an NSError.
     *  The object we are getting is being returned from our network code. In this case it is an integer, 0 means we had a successfull login.
     *  Now we can call [subject sencNext:] to inform our login view controller of the pass or fail of the authentication.
     *  [sendCompleted] is a cleanup call, allowing ReactiveCocoa to dispose of our blocks and free up memory.
     */

    [loginResult
      where:^(id x) {
          return [x hasObject];
      }]
     subscribeNext:^(id _) {
         NSNumber *number;
         NSString *errcode = [_ object];
         if (errcode.intValue == 0) number = [NSNumber numberWithBool:YES] ?: [NSNumber numberWithBool:NO];
         [subject sendNext:[NSArray arrayWithObjects:number, username, password, nil]];
         [subject sendCompleted];
     }];

    /*
     *  [execute:] starts the whole thing off. This call invokes the loginCommand block that returns the async call to the loginResult subscribable.
     */

    [loginCommand execute:@"This value gets transfered to the addAsyncBlock:^(id _) block above."];

    return subject;
}

/*
 *  This function uses a QuickBase wrapper I made to make server request and whatnot.
 *  This function is called when the [loginCommand execute:] call in the previous function gets called
 *  and invokes the loginCommand block which returns the async request.
 *  That request is what this function is returning.
 */

+ (RACAsyncSubject *)authenticateUser:(NSString *)username password:(NSString *)password
{
    QBRequest *request = [[QBRequest alloc] init];

    [request setQuickBaseAction:QBActionTypeAuthenticate];

    [request setURLString:URLstring withDatabaseID:nil];

    [request setApplicationToken:appToken];

    return [request sendAndPersist:NO username:username password:password];
}

现在我们在我的实际网络包装器中,它知道请求何时完成或失败或其他什么。

- (RACAsyncSubject *)sendAndPersist:(BOOL)persist username:(NSString *)username password:(NSString *)password
{
    self.subject = [RACAsyncSubject subject];

    /*
     *  These next two blocks are called when my network request is done.
     *  My AsyncSubject is a property so that I can reference it later when I parse
     *  throught the response and figure out whether I logged in correctly or not.
     *  In the case that the network call itself fails, then the AysncSubject calls
     *  [sendError:] which will invoke those NSError capturing blocks in the ThaweQBRequestController.
     */

    [anOp onCompletion:^(MKNetworkOperation *completedOperation) {

        dispatch_async(background_parseSave_queue(), ^{

            [self updateDatabase];
        });

    } onError:^(NSError *error) {
        [subject sendError:error];
    }];

    [engine enqueueOperation:anOp];

    return subject;
}

最后让您了解我的解析器中何时有主题 [sendNext:]。self.currentParsedCharacterData 是一个具有整数值的 NSString。

else if ([elementName isEqualToString:@"errcode"])
    {
        if ([self.action isEqualToString:@"API_Authenticate"]) {
            [subject sendNext:[self.currentParsedCharacterData copy]];
            [subject sendCompleted];
        }
    }

我知道这很长,但我真的很想给出一些实际的代码示例。

于 2012-08-22T00:39:23.563 回答