如何编写一个Objective-C的Completion Block

47

我处于这样一种情况,需要从我的视图控制器调用一个类方法,让它执行完其任务后再执行一些操作,但这些操作仅在该类方法完成后才能执行。

(我认为我需要的是一个完成块,如果我错了,请指出。)

这就是情况:

我正在使用 Parse.com 作为我的应用程序后端。当用户注册帐户时,他们在弹出窗口中输入姓名、公司和其他一些信息,然后单击提交。提交按钮连接到一个类方法(如下所示),该方法接受他们的 PFUser 对象和公司名称,并创建一些数据库对象。该函数完成后,使用委托关闭弹出窗口。

问题是,我需要按特定顺序创建这些对象,因为它们依赖于彼此的 objectId 是否存在。问题是,关闭弹出窗口的委托方法会立即在单击提交后调用,因为它是堆栈上的下一个方法。

保存 Parse 对象时,需要调用一个类似于以下代码的方法: (这是我希望写的代码,我认为它可以解决我的问题)

[someParseObject saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
    // Code here runs AFTER the method completes.
    // This also happens on another thread which
    // I'd like to implement as well.
}];

所以,我需要弄清如何做类似以下的事情: (我相信与该方块有关的所有内容都是完全错误的)


(与这个块有关的所有内容肯定是完全错误的)
SignUpViewController.m

myUserOrg *userOrg = [myUserOrg object]; // myUserOrg = Custom PFObject Subclass

// My method that takes in a user object and a string, creates
// the database objects in order.
[userOrg registerNewUserOrgWithUser:(PFUser*) andCompanyName:(NSString*) companyName withBlock(somethingHere)block {

    if(error) {
        NSLog(@"Unable to create org!");
    } else {
        NSLog(@"Created Org!");
        [self.delegate dismissSignupView];
}

如果您需要额外的信息或澄清,请告知我。

先行致谢!

--------- 编辑一 ----------

好的,所以几个相当大的时间单位之后,这就是我想出来的。整个实现可能更简单,可以减少很多 API 调用,但我会努力改进。还有其他一些明显的问题,但这是第一步。

方法调用:

[testOrg registerNewUserOrgWithUser:currentUser
         creatingOrgContactWithName:@"MyBigHappy Corp."
                          withBlock:^(BOOL succeeded, NSError *error) {
                              if (error) {
                                  NSLog(@"Not working");
                              } else {
                                  NSLog(@"Working!");
                              }
                          }];

方法实现:

@implementation MYUserOrg

@dynamic orgContact;
@dynamic orgDisplayName;
@dynamic members;
@dynamic contacts;

+ (NSString *)parseClassName {
    return @"MYUserOrg";
}

dispatch_queue_t NewUserOrgRegistrationQueue;

-(void)registerNewUserOrgWithUser:(MYUser*)user
       creatingOrgContactWithName:(NSString*) orgContactName
                        withBlock:(MYBooleanResultBlock) block {

    NewUserOrgRegistrationQueue = dispatch_queue_create("com.myapp.initialOrgCreationQueue", NULL);

    dispatch_async(NewUserOrgRegistrationQueue, ^{

        NSMutableArray *errors = [[NSMutableArray alloc] init];

        // Initial org save to generate objectId
        NSError *orgSaveError = nil;
        [self save:&orgSaveError];

        if (orgSaveError) {
            [errors addObject:@"Initial Org save Failed"];
        }

        // Create and Relate Org Contact
        NSError *saveOrgContactError = nil;
        MYontact *orgContact = [MYContact object];
        [orgContact setContactType:MYContactTypeUserOrganization];
        [orgContact setDisplayName:orgContactName];
        [orgContact setParentOrg:self];
        [orgContact save:&saveOrgContactError];

        if (saveOrgContactError) {
            [errors addObject:@"Saving Org Contact Failed"];
        } else {
            // If Org contact saved, set it;
            [self setOrgContact:orgContact];
        }

        // Create amd Relate User Contact
        NSError *saveUserContactError = nil;
        MYContact *userContact = [MYContact object];
        [userContact setFirstName:user.firstName];
        [userContact setLastName:user.lastName];
        [userContact setContactType:MYcontactTypeUser];
        [userContact setParentOrg:self];
        [userContact save:&saveUserContactError];

        if (saveUserContactError) {
            [errors addObject:@"Saving user contact failed"];
        }

        NSError *saveUserError = nil;
        [user setParentOrg:self];
        [user setUserContact:userContact];
        [user save:&saveUserError];

        if (saveUserError) {
            [errors addObject:@"Saving User failed"];
        }

        // Return if block succeeded and any errors.
        NSError *error = nil;
        BOOL succeeded;
        if (errors.count > 0) {

            NSDictionary *userInfo = @{@"error" : errors};
            errors = [NSError errorWithDomain:@"MyAppErrorDomain"
                                         code:1
                                     userInfo:userInfo];
            succeeded = NO;
        } else {
            succeeded = YES;
        }
        block(succeeded, error);
    });

}

@end

看一下saveAllInBackground:block:,看看是否可以只做一个保存并从该块中解除。你可能不需要定义自己的块,只需使用Parse SDK调用的块即可。 - Wain
我之前其实不知道这个方法,所以感谢你指出来!问题在于这些对象需要按特定顺序创建,因为它们的某些属性彼此依赖。我不能一次性保存它们,因为在某些情况下,当保存发生时,属性值并不存在。 - Andrew
5个回答

100

13
很赞的网址。我得等回家后再打开它。我甚至不想让它出现在我的浏览历史中。哈哈! - logixologist
笑死,这是一种处理你记不住的东西的绝妙方式。 - user1988
哈哈哈哈,我真的明白为什么他们称之为 f...(请注意,这里是省略号,不是完整的单词)。 - AndaluZ
在我看来,完成块是C/C++非常需要的一个很棒的功能。是否有一个可以导入的C/C++库,提供完成块功能? - Frak

36

我为一个类编写了一个completionBlock,它将返回掷骰子后的值:

  1. @interface声明之前的.h文件中定义带有返回类型的typedef。

typedef void (^CompleteDiceRolling)(NSInteger diceValue);
在块的头文件(.h)中定义一个@property
@property (copy, nonatomic) CompleteDiceRolling completeDiceRolling;
定义一个具有finishBlock参数的方法(.h)。
- (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock;
  • 将之前定义的方法插入到.m文件中,并将 finishBlock 提交给之前定义的 @property

  • - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock{
        self.completeDiceRolling = finishBlock;
    }
    
  • 要触发completionBlock,请将预定义的变量类型传递给它(不要忘记检查completionBlock是否存在)

  • if( self.completeDiceRolling ){
        self.completeDiceRolling(self.dieValue);
    }
    

    18
    你将该块定义为自定义类型:
    typedef void (^ButtonCompletionBlock)(int buttonIndex);
    

    然后将其用作方法的参数:

    + (SomeButtonView*)buttonViewWithTitle:(NSString *)title 
                          cancelAction:(ButtonCompletionBlock)cancelBlock
                      completionAction:(ButtonCompletionBlock)completionBlock
    

    在代码中调用它时,它就像任何其他代码块一样:

    [SomeButtonView buttonViewWithTitle:@"Title"
                       cancelAction:^(int buttonIndex) {
                             NSLog(@"User cancelled");
                   } 
                     completionAction:^(int buttonIndex) {
                             NSLog(@"User tapped index %i", buttonIndex);
                   }];
    

    如果到了触发该块的时间,只需调用completionBlock()即可(其中completionBlock是块的本地副本的名称)。


    15

    关于 http://goshdarnblocksyntax.com/

    作为一个局部变量

    returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
    

    作为一个属性

    @property (nonatomic, copy) returnType (^blockName)(parameterTypes);
    

    作为方法参数:
    - (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName;
    

    作为方法调用的参数:
    [someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
    

    作为typedef:

    typedef returnType (^TypeName)(parameterTypes);
    TypeName blockName = ^returnType(parameters) {...};
    

    6

    简单的完成块

        // Completion block method
        -(void)myMethod:(void (^)(void))completion {
            NSLog(@"iPhone");
            completion();
            NSLog(@"iPod");
        }
    
       // Calling completion block method
        - (void)viewDidLoad {
            [super viewDidLoad];
    
            [self myMethod:^{
               NSLog(@"iPad");
            }];
        }
    
      // output
      iPhone
      iPad
      iPod
    

    1
    谢谢!正是我所需要的。 - Billyobobo

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接